JZOJ2963Tree
来源:互联网 发布:便携工具 知乎 编辑:程序博客网 时间:2024/05/01 12:40
题目大意及模型转换
给定由N个结点组成的树。每次询问如果断掉第z条边并在x与y间连边n个结点是否连通。n<=200000,询问个数m<=2000000。
这道题比较水。。。
是输出NO,否输出YES(这是题目背景的问题)。
考虑简化
断了一条边后,很显然分成了两个连通块。添加一条新的边使得这两个连通块可以缩为一个连通块,那么这条边连接的两个结点必须分别在两个连通块中。现在问题变为了判断两个结点是否在同一个连通块中。我们首先可以把无根树定一个根,蒟蒻我通常用1。转化为有根树后。每次断开的边如果是连接j与k,那么其中一个连通块是以这两个点其中一个为根的子树。而另一个,是整个树排除那个子树,不规则,很难判是否在其中。但是仔细一想,只有两个连通块,不在其中一个就肯定在另一个。因此只要判其中一个在子树内那么另一个是否不在子树内即可。
时间戳
说到判断一个点在不在某子树内,其实相当于判断这个点是否有一个祖先为该子树的根节点。这个问题可以用时间戳解决。
设
我们规定每走一步时间+1(即顺着一条边从一个结点走到另一个结点)。
很显然,如果j是k的祖先(j=k也视为j是k的祖先)必须满足:
如果不允许j=k也视为j是k的祖先则去掉等于号。
那么就完美解决了。
注意
如何得知第z条边连着的j与k哪个造成的连通块是一棵子树?
看看谁的f值大。
参考程序
一次AC(汪汪汪)
#include<cstdio>#include<cstring>#define fo(i,a,b) for(i=a;i<=b;i++)using namespace std;int b[200005][3];int f[200005],g[200005],h[200005],go[400010],next[400010];int i,j,k,l,t,n,m,tot,top,x,y,z;int read(){ int x=0; char ch=getchar(); while (ch<'0'||ch>'9') ch=getchar(); while (ch>='0'&&ch<='9'){ x=x*10+ch-'0'; ch=getchar(); } return x;}void add(int x,int y){ go[++tot]=y; next[tot]=h[x]; h[x]=tot;}void dfs(int x,int y){ f[x]=++top; int t=h[x]; while (t){ if (go[t]!=y) dfs(go[t],x); t=next[t]; } g[x]=++top;}bool pd(int x,int y){ return (f[x]>=f[y])&&(g[x]<=g[y]);}int main(){ n=read(); fo(i,1,n-1){ j=read();k=read(); b[i][1]=j;b[i][2]=k; add(j,k); add(k,j); } dfs(1,0); m=read(); fo(i,1,m){ x=read();y=read();z=read(); j=b[z][1];k=b[z][2]; if (f[j]<f[k]) l=k;else l=j; if (pd(x,l)!=pd(y,l)) printf("NO\n");else printf("YES\n"); }}
0 0