CF 566E Restoring Map Code (End)

在WC时,jmr在他的《集训队作业选作》中给出了这个题的精确解答
这算是我为数不多听明白的题目了
人生中第一道黑题!!!

思路

因为题目中并没有将集合与点的对应关系告诉我们
所以相当于已知一个大集合,其中下辖着各个小集合,小集合中涵盖着点
这就引导着我们朝着集合的角度来思考答案

在我们还没有分析题目之前,先做几个简单但重要的事,这是之后证明的前提

1 判菊花图
2 特判n=2或3

(f_u)(u)的距离(≤2)的集合
首先可以分析出当两个点的距离大于4时,没有所在集合没有交点
当距离等于4时,可以判出一个点(但这并没有什么用)
当距离等于3时,可以判出两个节点,并且均不为叶节点
小于2时,我们发现似乎讨论不太出更多性质(3就够了

据此我们可以将一棵树中的所有非叶节点都筛出来,将该集合记为(releaf),我们可以知该集合中任意元素均为非叶节点,接下来我们证明其必要性

(prove):

假设我们已经充分的对集合两两求交,(releaf)已经完善
设存在一个非叶节点(u)(s.t.) (u otin releaf)(u)必然存在一条边(E),在(E)上另一点(vin releaf)因为(v,u)非叶节点,那么对其子节点进行集合求交,则可以筛出(u,v),但(releaf)并不存在(u,v),所以假设不成立

现在我们已有(releaf),非常自然地将这个题转化为向(releaf)中的点挂叶节点,并且我们可以建出(releaf)树,借助这个树,我们可以处理与树中点(u)距离(≤1)的点集(g_u),然后我们可以发现一个点(v)可以挂在(u)上,那么其满足(g_u)(=)(f_v and releaf)
但是我们又遇到了一个问题,如果(vin releaf),那么也满足上式,为了解决这个问题,我们决定研究一下怎么判出集合与点的对应关系

先给出结论:
对于一个叶节点 x,包含 x 的(f_x)中元素个数最小的(f)就是 x 的(f_x)

我不打算证明了
算法复杂度(O(frac{n^3}{w} ))

(detail)

算法中(bitset)中寻找(1)不能使用(for)循环,若使用复杂度不会除(w)
所以使用(\_ Find \_ first)(\_ Find \_ next)
前者是找第一个1,后者是找下一个1

#include<iostream>
#include<cstdio>
#include<cstring>
#include<bitset> 
#include<set>

using namespace std;

#define fx(x) _Find_next(x)
#define ff() _Find_first() 
#define int long long
#define INF 1<<30

const int p=1e3+5;

template<typename _T>
inline void read(_T &x)
{
	x=0;char s=getchar();int f=1;
	while(s<'0'||s>'9') {f=1;if(s=='-')f=-1;s=getchar();}
	while('0'<=s&&s<='9'){x=(x<<3)+(x<<1)+s-'0';s=getchar();}
	x*=f;
}

bitset<p> b[p];
bitset<p> g[p];
bitset<p> ans[p];
bitset<p> releaf;
int tit;
int a[p];
int vis[p];
bool c[p][p];
bitset<p> lef,rig;

signed  main()
{
	int n;
	read(n);
	
	if(n == 2){cout<<1<<" "<<2;return 0;}
	if(n == 3){cout<<1<<" "<<2<<'
'<<2<<" "<<3<<'
';return 0;}
	
	for(int i=1,x;i<=n;i++)
	{
		read(x);
		for(int j=1,y;j<=x;j++)
		{
			read(y);
			b[i].set(y-1);
		}
	}
	releaf = b[1];
	for(int i=2;i<=n;i++) releaf&=b[i];
	if(releaf.count() == n){
		for(int i=2;i<=n;i++)
		{
			cout<<1<<" "<<i<<'
';
		}
		return 0;
	}
	releaf.reset();
	bitset<p> op;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)
		{
			if(i == j) continue;
			op = b[i]&b[j];
			int x = op._Find_first();
			int y = op._Find_next(x);
			
			if(op.count()==2)
			{
				g[x+1].set(y);
				g[x+1].set(x);
				g[y+1].set(x);
				g[y+1].set(y);		
				releaf|=op;
			}
		}
	int len = releaf.size();
	bitset<p> ks;
	if(releaf.count()==2) 
	{
		for(int i=1;i<=n;i++)
		{
			if(b[i].count()!=n)
			{
				lef |= b[i];
				lef ^=releaf;
				break;	
			}
		}
		
		int st = releaf.ff();
		int ry = releaf.size();
		int stt = releaf.fx(st);
		cout<<st+1<<" "<<stt+1<<'
';
		for(int i=0;i<n;i++)
		{
			if(lef[i]) cout<<st+1<<" "<<i+1<<'
';
			else 
			{
				if(i!=st&&i!=stt)
				{
					cout<<stt+1<<" "<<i+1<<'
';
				}
			}
		}
		return 0;
	}
	for(int i=0;i<len;i++)
	{
		ks.reset();ks.set(i);
		ks = ks&releaf;
		if(ks.count()) a[++tit] = i+1;
	}
	
	bitset<p> aux;
	aux = releaf;
	aux.flip();	
	int opt  = aux.ff();
	int gi = -1;
	for(;opt!=gi&&opt<n;opt = aux.fx(opt))
	{
		int minn = INF;
		int lsq= 0 ;
		for(int i=1;i<=n;i++) 
		{
			int ikj = 0;
			if(b[i][opt])
			{
				if(minn>(b[i]&releaf).count())
				{
					ikj =1;
					ks = b[i]&releaf;
					minn = ks.count();
					vis[opt+1] = i;
				}
			}
		}		
		for(int j=1;j<=tit;j++)
		{
			if(g[a[j]] == ks)
			cout<<a[j]<<" "<<opt+1<<'
';
		}		
		gi = opt;
	}
	for(int i=1;i<=tit;i++)
	{
		g[a[i]][a[i]-1] = 0;
		gi =- 1;
		opt = g[a[i]].ff();
		for(;opt!=gi&&opt<n;opt = g[a[i]].fx(opt))
		{
			if((!c[a[i]][opt+1])&&(!c[opt+1][a[i]]))
			{
				cout<<a[i]<<" "<<opt+1<<'
';
				c[a[i]][opt+1] = c[opt+1][a[i]] = 1;
			}
			gi = opt;
		}
	}
	
 }

(End)

这是我的第一道黑题,也是第一道(bitset)优化构造题