CF566E Restoring Map 解题报告

有一棵 \(n\) 个节点的树,只给出每个点与其距离不超过 \(2\) 的点的集合(包含自己),要求构造出一棵符合要求的树,保证有解。

\(n\le 1000\)

思维不难,但是细节很多,不愧是评分 3200 的题(也可能是我的方法太怪了)。

一个显然的方法就是减小数据大小递归求解,考虑如何找出树的某些叶子,可以发现,如果存在 \(x\) 个集合一样且这些集合的大小为 \(x+2\) ,那么这些集合对应的点就是一堆叶子,如果只看这些叶子和它们的父亲就是一个菊花图,显然任何形态的树都存在这一结构,如果能找到另外一个集合和这种集合的交的大小为 \(2\) ,显然在交中的点不是叶子,这就意味着,当目前的树的高度大于 \(2\) 时,我们一定可以靠这种方法找出这一结构并找出叶子,然后将可以减小规模递归求解。

当树的高度小于等于 \(2\) 时,那么目前的树是一个菊花图,剩下的所有集合都应该是相同的,可以发现,如果有一个原集合和现在的集合交的大小为 \(1\) ,那么在交中的点一定不是根,如果交的大小为 \(2\) ,那么在交中的点一定包含了根,然后枚举一遍做交就可以找出根了。

然后从根往回递归,记得存之前找出的叶子的父亲和爷爷节点,根据已经形成的树判断那两个点哪个为父亲哪个为爷爷。

找叶子时需要找到出一个交为 \(2\) 的点,可以预处理出哪两个集合交为 \(2\) ,判断交要用 bitset 优化,复杂度可降为 \(\mathcal{O(\frac{n^3}{w})}\)

判断两个集合是否相同可以用随机化 hash ,对每个点打上一个 \([1,2^{64}]\) 的随机数,然后一个集合的 hash 值就是集合的点的随机数的异或和,删除一个点就再异或这个点的随机数,可以发现出锅概率大概是 \(\frac{1}{2^{64}}\)

需要注意的细节:

  1. 要对 \(n=2\) 特判。

  2. 每次找叶子就只找同一个父亲的叶子删除,不然连样例 2 都过不了(其实是我的方法有一点小锅,但是我还没有想到怎么样才能解决)。

其实我一开始找叶子的方法不是这样的,后来假了三四次才找到了正确方法,所以做题时一定要想清楚了再写啊qaq(但这并不妨碍我骂出题人)。

MDFK = 莫队分块(确信)

已经去掉了 3K 的调试代码后的代码:

#include<bits/stdc++.h>
using namespace std;

#define ll long long
const int M=1005;

int read(){
	int x=0,y=1;char ch=getchar();
	while(ch<'0'||ch>'9') y=(ch=='-')?-1:1,ch=getchar();
	while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
	return x*y;
}

ll RAND(){
	ll res=0;
	for(int i=1;i<=17;i++) res=res*10ll+rand()%10;
	return res;
}

int tot=0,first[M];
struct Edge{ int nxt,to; }e[M*M];
void add(int x,int y){
	e[++tot]=(Edge){first[x],y};
	first[x]=tot;
}

ll IH[M],Hash[M];
int n,sy,SZ[M],fa[M][2];bool vis1[M],vis2[M],vis3[M];
vector<int> cun[M],hhh[M];bitset<M> jh[M],fuc[M],fz;
struct FUCK{ ll h;int id; }FFF[M];
bool cmp(FUCK x,FUCK y){ return x.h<y.h; }
int MDFK[M];
void work(int cs){
	int cnt=0,CNT=0;
	for(int i=1;i<=n;i++) if(!vis1[i]) FFF[++CNT]=(FUCK){Hash[i],i};
	sort(FFF+1,FFF+CNT+1,cmp);
	for(int i=1;i<=CNT;i++){
		int j=i;
		while(j<CNT&&FFF[j+1].h==FFF[i].h) j++;
		if(j-i+1==SZ[FFF[i].id]-2) for(int k=i;k<=j;k++) MDFK[++cnt]=FFF[k].id;
		i=j;
	}
	int minn=1e9,u=1;
	for(int i=1;i<=cnt;i++){
		if(Hash[MDFK[i]]!=Hash[MDFK[i+1]]||i==cnt){
			if(SZ[MDFK[i]]<minn) minn=SZ[MDFK[i]],u=i;
		}
	}
	int fzcnt=0;ll NH=Hash[MDFK[u]];
	for(int i=1;i<=cnt;i++) if(Hash[MDFK[i]]==NH) MDFK[++fzcnt]=MDFK[i];
	cnt=fzcnt;
	if(!cnt){
		int rt=1,u;
		for(int i=1;i<=n;i++) if(!vis1[i]) u=i;
		for(int i=1;i<=n;i++){
			if(!vis1[i]) continue ;
			fz=fuc[i]&jh[u];
			if(fz.count()==1) for(int j=1;j<=n;j++) if(fz[j]) vis3[j]=1;
			if(fz.count()==2) for(int j=1;j<=n;j++) if(!fz[j]) vis3[j]=1;
		}
		for(int i=1;i<=n;i++) if(!vis2[i]&&!vis3[i]) rt=i;
		for(int i=1;i<=n;i++) if(!vis2[i]&&i!=rt) printf("%d %d\n",fa[i][0]=rt,i);
		return ;
	}
	for(int l=1;l<=cnt;l++){
		int i=MDFK[l],u;
		for(int j=first[i];j;j=e[j].nxt){
			int v=e[j].to;
			if(!vis1[v]) u=v;
		}
		fz=fuc[i]&fuc[u];
		int x=fz._Find_first(),y=fz._Find_next(x);
		for(int j=1;j<=n;j++){
			if(jh[i][j]&&j!=x&&j!=y){
				if(vis2[j]) continue ;
				vis2[j]=1;hhh[cs].push_back(j);
				fa[j][0]=x,fa[j][1]=y;
				int sz=cun[j].size();
				for(int k=0;k<sz;k++){
					int v=cun[j][k];
					jh[v][j]=0;Hash[v]^=IH[j];SZ[v]--;
				}
			}
		}
	}
	for(int i=1;i<=cnt;i++) vis1[MDFK[i]]=1;
	work(cs+1);
	int FUKC=hhh[cs].size();
	for(int i=0;i<FUKC;i++){
		int j=hhh[cs][i];
		if(fa[fa[j][1]][0]==fa[j][0]) swap(fa[j][0],fa[j][1]);
		printf("%d %d\n",fa[j][0],j);
	}
}
void solve(){
	sy=n=read();
	for(int i=1;i<=n;i++) IH[i]=RAND();
	for(int i=1;i<=n;i++){
		int sz=read(),x;
		for(int j=1;j<=sz;j++) x=read(),jh[i][x]=fuc[i][x]=1,cun[x].push_back(i),Hash[i]^=IH[x];
	}
	if(n==2) return (void)(printf("1 2\n"));
	for(int i=1;i<=n;i++)
		for(int j=i;j<=n;j++){
			int x=(jh[i]&jh[j]).count();
			if(i==j) SZ[i]=x;
			else if(x==2) add(i,j),add(j,i);
		}
	work(0);
}

signed main(){
	srand(time(NULL));
	solve();
}
posted @ 2021-02-04 10:54  Dabuliuzp  阅读(84)  评论(0编辑  收藏  举报
/* */ 返回顶端