[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;
}
posted @ 2022-03-05 21:11  SHAConan  阅读(43)  评论(0)    收藏  举报