BZOJ 3319: 黑白树 并查集 + 离线 + 思维

Description

给定一棵树,边的颜色为黑或白,初始时全部为白色。维护两个操作:
1.查询u到根路径上的第一条黑色边的标号。
2.将u到v    路径上的所有边的颜色设为黑色。
Notice:这棵树的根节点为1

Input

第一行两个数n,m分别表示点数和操作数。
接下来n-1行,每行2个数u,v.表示一条u到v的边。
接下来m行,每行为以下格式:
1 v 表示第一个操作
2 v u 表示第二种操作
n,m<=10^6

Output

对于每个询问,输出相应答案。如果不存在,输出0
题解:我们将边下放到点,用点的标号来代指边
看完题面后有一个想法:用并查集维护每个白点所能到达的第一个黑点
我们知道,并查集是向上合并的
如果正着做,每一次将一条路径染黑,会导致很多白点的祖先改变,而且是向下变
向下变就十分麻烦,非常不好做
不妨逆着操作,记录每一个节点变黑的最早时间,将操作逆着进行
可以先将整棵树全部染黑,逆着逐渐染白,那么每个白点的祖先只会更向上,向上合并
比如当前要将点 $x$ 染白,那么 $x$ 子树中的白点在改动前并查集指向的祖先都是 $x$
在改动后 $x$ 的祖先会向上合并,而 $x$ 子树中的白点在并查集查祖先时查到 $x$ 的话会顺着 $x$ 向上合并到的祖先继续向上查询
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<map>
#include<vector>
#include<stack>
#include<queue>
#define setIO(s) freopen(s".in","r",stdin),freopen(s".out","w",stdout)  
#define maxn 1002002
using namespace std;  
char *p1,*p2,buf[100000];
#define nc() (p1==p2&&(p2=(p1=buf)+fread(buf,1,100000,stdin),p1==p2)?EOF:*p1++)
int rd() {int x=0; char c=nc(); while(c<48) c=nc(); while(c>47) x=(((x<<2)+x)<<1)+(c^48),c=nc(); return x;}
vector<int>G[maxn]; 
stack<int>S;                  
int n,m,edges; 
int tm[maxn],hd[maxn<<1],to[maxn<<1],nex[maxn<<1];     
int siz[maxn],top[maxn],fa[maxn],dep[maxn],son[maxn]; 
int idx[maxn<<1],id[maxn<<1];     
inline void addedge(int u,int v,int c) {
    nex[++edges]=hd[u],hd[u]=edges,to[edges]=v, id[edges]=c;   
}
void dfs1(int u,int ff) {
    dep[u]=dep[ff]+1,siz[u]=1,fa[u]=ff; 
    for(int i=hd[u];i;i=nex[i]) {
        int v=to[i];     
        if(v==ff) continue; 
        idx[v]=id[i], dfs1(v,u), siz[u]+=siz[v]; 
        if(siz[v] > siz[son[u]]) son[u] = v;  
    }
}
void dfs2(int u,int tp) {
    top[u]=tp; 
    if(son[u]) dfs2(son[u], tp); 
    for(int i=hd[u];i;i=nex[i]) {
        int v=to[i]; 
        if(v==fa[u] || v==son[u]) continue; 
        dfs2(v,v); 
    }
}
inline int LCA(int x,int y) {
    while(top[x]^top[y]) {
        dep[top[x]]>dep[top[y]]?x=fa[top[x]]:y=fa[top[y]]; 
    }
    return dep[x]<dep[y]?x:y; 
}
struct Opt {
    int opt,x,y;
}op[maxn]; 
struct Union {
    int p[maxn]; 
    inline void init() {
        for(int i=0;i<maxn;++i) p[i]=i; 
    }
    int find(int x) {
        return p[x]==x?x:p[x]=find(p[x]); 
    }
}black,white;         
inline void mark(int u,int lca,int cur) {
    u=tm[u]?black.find(u):u;  
    while(dep[u]>dep[lca]) {
        tm[u]=cur;      
        if(!tm[fa[u]]) {
            black.p[u]=fa[u];   
            u=fa[u];   
        }    
        else {
            int y=black.find(fa[u]);          
            black.p[u]=y;          
            u=y;                  
        }
    }
}
inline void update(int u,int v,int cur) {
    int lca=LCA(u,v); 
    mark(u,lca,cur), mark(v,lca,cur);     
}
inline void change(int u) {
    white.p[u]=white.find(fa[u]);                
}
int main() {
    // setIO("input"); 
    scanf("%d%d",&n,&m); 
    for(int i=1;i<n;++i) {
        int u,v; 
        u=rd(),v=rd(); 
        addedge(u,v,i),addedge(v,u,i);         
    }
    dfs1(1,0), dfs2(1,1), black.init();                      
    for(int i=1;i<=m;++i) {
        op[i].opt=rd(); 
        if(op[i].opt==1) op[i].x=rd(); 
        if(op[i].opt==2) op[i].x=rd(), op[i].y=rd(), update(op[i].x,op[i].y,i);  
    }   
    white.init(); 
    for(int i=2;i<=n;++i) G[tm[i]==0?m+1:tm[i]].push_back(i);  
    m+=(G[m+1].size()>1); 
    for(int i=m;i>=1;--i) {      
        if(G[i].size()) {
            for(int j=0;j<G[i].size();++j) change(G[i][j]);        
        }else if(op[i].opt==1) {
            S.push(white.find(op[i].x));                 
        }
    }             
    while(!S.empty()) {
        printf("%d\n",idx[S.top()]); S.pop(); 
    }
    return 0; 
}

  

posted @ 2019-07-24 09:14  EM-LGH  阅读(241)  评论(0编辑  收藏  举报