JZOJ2963Tree

题目大意及模型转换

给定由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");
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值