[HEOI2016/TJOI2016]树 Method 1
离线储存、并查集
题目很容易懂,看起来很简单,但是一看到数据范围,脸上就掉色了:\(N\leq 10^5\)。
冷静分析:

上图中我们设 \(ufs_x\) 的值就是此时 \(x\) 的最近标记祖先,\(pre_x\) 代表此时 \(x\) 的父亲节点。操作分为:标记、查询
对于标记而言:
如果说 \(x\) 被染色,那么单独对于 \(x\) 而言,他的最近标记祖先就是 \(x\);如果不是,那么对于 \(x\) 而言,他的最近标记祖先就是 \(pre_x\) 的最近标记祖先。
由此我们可以得出求最近标记祖先,即查询的代码:
void find(int x){
if(ufs[x]==x) return x;
else return find(pre[x]);
}
同样的我们可以得出标记操作的修改是在 \(ufs_x\) 的基础上完成的。于是我们可以设置 \(colcnt\) 数组保存第 \(x\) 个数被染了几次色,如果染色此时大于 1,我们就可以将 \(ufs_x\) 赋值为自己,否则就将 \(ufs_x\) 设置为 \(pre_x\),其实这个设置为什么数都可以,只要不使得像上面代码中的 \(ufs_x\) 与 \(x\) 相等就可以了。
所以代码也就出来了:
if(colcnt[x]) ufs[x]=x;
else ufs[x]=fa;
现在我们还缺的是输入和建立树结构(即子节点和父节点),树结构得用链式前向星和dfs。
代码如下:
#include<bits/stdc++.h>
using namespace std;
const int maxn=100010;
struct node{
int next,v;
}edge[maxn<<1];
int head[maxn],tot=0;
void addage(int u,int v){
tot++;
edge[tot].next=head[u];
edge[tot].v=v;
head[u]=tot;
}
int colcnt[maxn],pre[maxn],ufs[maxn];
struct line{
int x,answer;
bool typ=false;
}opt[maxn];
void dfs(int x,int fa){
if(colcnt[x]) ufs[x]=x;
else ufs[x]=fa;
pre[x]=fa;
for(int i=head[x];i;i=edge[i].next){
int v=edge[i].v;
if(v==fa) continue;
dfs(v,x);
}
}
int find(int x){
return x==ufs[x]?x:ufs[x]=find(ufs[x]);//必须这么写,要么加快读,也有可能快读也不行,否则会TLE。
}
int main(){
int N,Q;
scanf("%d %d",&N,&Q);
for(int i=1;i<N;i++){
int u,v;
scanf("%d %d",&u,&v);
addage(u,v),addage(v,u);
}
colcnt[1]=1;
for(int i=1;i<=Q;i++){
char oper;
cin>>oper;
if(oper=='C'){
scanf("%d",&opt[i].x);
opt[i].typ=true;
colcnt[opt[i].x]++;
}
else scanf("%d",&opt[i].x);
}
dfs(1,0);
pre[1]=1;
for(int i=Q;i>=1;i--){
if(opt[i].typ){
colcnt[opt[i].x]--;
if(!colcnt[opt[i].x]) ufs[opt[i].x]=pre[opt[i].x];
}
else opt[i].answer=find(opt[i].x);
}
for(int i=1;i<=Q;i++)
if(!opt[i].typ) printf("%d\n",opt[i].answer);
return 0;
}

浙公网安备 33010602011771号