主仆见证了 Hobo 的离别 题解

前言:

题面挺神仙。反正我考试的时候看了40分钟也没看懂。
后来改题感觉自己写的挺假,没想到加个\(k==1\)的特判竟然就A了?无语力。

解析:

看懂题以后就好说了。首先这显然是一个树形结构。我们考虑把“交”的操作放到一棵树上,把“并”的操作放到一棵树上。
考虑建边。比如将\((1,2,3)\)并成\((4)\),那么就在并树上,将\(1,2,3\)的父亲设置成\(4\)
然后,对于每个询问\((x,y)\),如果在交树上,\(x\)\(y\)的祖先,或在并树上,\(y\)\(x\)的祖先,那么答案就是\(1\),否则是\(0\)
看起来有手就行。不过细节还是要注意。

关于实现上的细节:

1.

我们在建立模型的时候,显然建的是有向边,由儿子指向父亲。但我们在真正建树的时候,是要建无向边。因为无向边比较好维护。那此时怎么判断父子关系呢?
首先,有一个性质。每个节点的父亲的编号一定比自己的编号大。也就是说,大的点一定在上面。因为维护的是一片森林,所以可以根据这个性质找到每棵树的根节点。
所以可以考虑按编号从大到小DFS一遍。按照DFS序判断父子关系。
写过树剖的人都知道,一颗子树内的dfs序是连续的。因此假如y是x的父亲,那么\(dfn[y]<=dfn[x]<=dfn[x]+size[x]-1<=dfn[y]+size[y]-1\)

2.

注意一个细节。\(k==1\)的时候,交和并等价。那么既要在交树上建边,又要在并树上建边。
然后就可以A掉这道题了。

关于正确性:

1.为什么只需要判断x和y的关系,不需要判断y的祖先和x的关系呢?

首先,对于在y到根节点上的路径上的点(不包括根节点),那么这些节点既有入度,也有出度,那么这些点就不可能在另外一棵树上出现了。
其次,对于根节点,它只可能去合成别的节点,那么它在另一棵树上一定没有入度。也就是说,即使根节点在另一棵树上出现,它也必然是叶子节点,不可能是x的父亲,所以不用判。

代码:

#include <bits/stdc++.h>
using namespace std;
const int maxn=500000+10;
int n,m,cntj,cntb,totot,totj,totb,Time;
struct node{
	int to,next;
}edgej[maxn],edgeb[maxn];
int headj[maxn],headb[maxn],dfnb[maxn],dfnj[maxn],sizeb[maxn],sizej[maxn];
struct que{
	int x,y;
}b[maxn];
bool vis[maxn];
void addj(int from,int to){
	edgej[++cntj].to=to;
	edgej[cntj].next=headj[from];
	headj[from]=cntj;
}
void addb(int from,int to){
	edgeb[++cntb].to=to;
	edgeb[cntb].next=headb[from];
	headb[from]=cntb;
}
void dfs1(int u,int f){
	dfnj[u]=++Time;
	sizej[u]=1;
	vis[u]=1;
	for(int i=headj[u];i;i=edgej[i].next){
		int v=edgej[i].to;
		if(v==f) continue;
		dfs1(v,u);
		sizej[u]+=sizej[v];
	}
}
void dfs2(int u,int f){
	dfnb[u]=++Time;
	sizeb[u]=1;
	vis[u]=1;
	for(int i=headb[u];i;i=edgeb[i].next){
		int v=edgeb[i].to;
		if(v==f) continue;
		dfs2(v,u);
		sizeb[u]+=sizeb[v];
	}
}
int get_ans(int x,int y){
	if(dfnj[x]<=dfnj[y]&&dfnj[x]+sizej[x]>=dfnj[y]+sizej[y]) return 1;
	if(dfnb[y]<=dfnb[x]&&dfnb[y]+sizeb[y]>=dfnb[x]+sizeb[x]) return 1;
	return 0;
}
void Solve(){
	scanf("%d%d",&n,&m);
	for(int i=1,op,k,ss;i<=m;++i){
		scanf("%d",&ss);
		if(ss){
			totot++;
			scanf("%d%d",&b[totot].x,&b[totot].y);
		}else{
			scanf("%d%d",&op,&k);
			if(op){
				totb++;
				if(k>1){
					for(int j=1;j<=k;++j){
						scanf("%d",&ss);
						addb(ss,totb+totj+n);
						addb(totb+totj+n,ss);
					}
				}else{
					for(int j=1;j<=k;++j){
						scanf("%d",&ss);
						addb(ss,totb+totj+n);
						addb(totb+totj+n,ss);
						addj(ss,totb+totj+n);
						addj(totb+totj+n,ss);
					}
				}
			}else{
				totj++;
				if(k>1){
					for(int j=1;j<=k;++j){
						scanf("%d",&ss);
						addj(ss,totj+totb+n);
						addj(totj+totb+n,ss);
					}
				}else{
					for(int j=1;j<=k;++j){
						scanf("%d",&ss);
						addb(ss,totb+totj+n);
						addb(totb+totj+n,ss);
						addj(ss,totb+totj+n);
						addj(totb+totj+n,ss);
					}
				}
			}
		}
	}
	//printf("n==%d totj=%d totb=%d cntj==%d cntb==%d\n",n,totj,totb,cntj,cntb);
	for(int i=totj+totb+n;i;--i) if(!vis[i]) dfs1(i,0);
	memset(vis,0,sizeof(vis));
	Time=0;
	for(int i=totb+totj+n;i;--i) if(!vis[i]) dfs2(i,0); 
	//for(int i=1;i<=totj+totb+n;++i) printf("dfnj[%d]=%d dfnb[%d]=%d sizej[%d]=%d sizeb[%d]=%d\n",i,dfnj[i],i,dfnb[i],i,sizej[i],i,sizeb[i]);
	for(int i=1;i<=totot;++i) printf("%d\n",get_ans(b[i].x,b[i].y));
}
int main(){
	freopen("friendship.in","r",stdin);
	freopen("friendship.out","w",stdout);
	Solve();
	return 0;
}

posted @ 2020-10-17 10:02  “起个名字真难♘”  阅读(137)  评论(2编辑  收藏  举报