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$ 向上合并到的祖先继续向上查询
我们知道,并查集是向上合并的
如果正着做,每一次将一条路径染黑,会导致很多白点的祖先改变,而且是向下变
向下变就十分麻烦,非常不好做
不妨逆着操作,记录每一个节点变黑的最早时间,将操作逆着进行
可以先将整棵树全部染黑,逆着逐渐染白,那么每个白点的祖先只会更向上,向上合并
比如当前要将点 $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; }