给定由N个结点组成的树。每次询问如果断掉第z条边并在x与y间连边n个结点是否连通。n<=200000,询问个数m<=2000000。
这道题比较水。。。
是输出NO,否输出YES(这是题目背景的问题)。
断了一条边后,很显然分成了两个连通块。添加一条新的边使得这两个连通块可以缩为一个连通块,那么这条边连接的两个结点必须分别在两个连通块中。现在问题变为了判断两个结点是否在同一个连通块中。我们首先可以把无根树定一个根,蒟蒻我通常用1。转化为有根树后。每次断开的边如果是连接j与k,那么其中一个连通块是以这两个点其中一个为根的子树。而另一个,是整个树排除那个子树,不规则,很难判是否在其中。但是仔细一想,只有两个连通块,不在其中一个就肯定在另一个。因此只要判其中一个在子树内那么另一个是否不在子树内即可。
说到判断一个点在不在某子树内,其实相当于判断这个点是否有一个祖先为该子树的根节点。这个问题可以用时间戳解决。
设 f[i] 为第一次进入结点i的时间。
g[i] 为离开结点i的时间。
我们规定每走一步时间+1(即顺着一条边从一个结点走到另一个结点)。
很显然,如果j是k的祖先(j=k也视为j是k的祖先)必须满足:
f[j]<=f[k]
g[j]>=g[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");
}
}