【NOIP2018模拟A组9.25】到不了

Description

wy 和 wjk 是好朋友。
今天他们在一起聊天,突然聊到了以前一起唱过的《到不了》。
“说到到不了,我给你讲一个故事吧。”
“嗯?”
“从前,神和凡人相爱了,愤怒的神王把他们关进了一个迷宫里,迷宫是由许多棵有根树组 成的。神王每次把两个人扔进其中的某一棵有根树里面,两个相邻节点的距离为 1,两人的 每一步都只能从儿子走到父亲,不能从父亲走到儿子,他们约定,走到同一个节点相见,由 于在迷宫里面行走十分消耗体力,他们决定找出那个使得他们走的总路程最少的节点,他们 当然会算出那个节点了,可是神王有时候会把两棵有根树合并为一棵,这下就麻烦了。。。”
“唔。。。”
[已经了解树,森林的相关概念的同学请跳过下面一段]
树:由 n 个点,n-1 条边组成的无向连通图。
父亲/儿子:把树的边距离定义为 1,root 是树的根,对于一棵树里面相邻的两个点 u,v,到 root 的距离近的那个点是父亲,到 root 距离远的那个点是儿子
森林:由若干棵树组成的图
[简化版题目描述]
维护一个森林,支持连边操作和查询两点 LCA 操作

Data Constraint

对于 30%的数据 1 ≤ N ≤ 1000 1 ≤ Q ≤ 1000
对于 100%的数据 1 ≤ N ≤ 100000 1 ≤ Q ≤ 10000

Solution

很容易想到LCT,但是LCT怎么求顶点为根时的LCA呢?
有一个很简单易懂的方法:
对于两个点u,v,我们要求以当前根为根的LCA,那么我们需要在access操作中返回一个值——最后一个操作的splay的根。这样我们发现,access(v),再access(u)时会返回u,v的LCA。为什么呢?因为在LCA以上一直到根的路径中,两个点到根的路径都包含,所以当当前点旋为根,然后再跳虚边没有了的时候,这个点就一定是u,v的LCA。
比较抽象,具体见代码。

#include<cstdio>
#include<iostream>
#include<cstring>
#define maxn 100010
#define maxm 100010
#define maxq 100010
using namespace std;
int n,m,q,tot;
int root[maxm],d[maxn];
struct Moon{
	int pf,fa,son[2],tag;
}t[maxn];
int fa[maxn];
void make(int x){
	swap(t[x].son[0],t[x].son[1]);
	t[x].tag^=1;
}
void clear(int x){
	if(t[x].tag){
		make(t[x].son[0]),make(t[x].son[1]);
		t[x].tag=0;
	}
}
void remove(int x,int y){
	for (d[0]=0;x!=y;x=t[x].fa) d[++d[0]]=x;
	for (int i=d[0];i>=1;--i) clear(d[i]);
}
int get(int x){
	return t[t[x].fa].son[1]==x;
}
void rotate(int x){
	int y=t[x].fa,k=get(x);
	if(t[y].fa) t[t[y].fa].son[get(y)]=x;
	if(t[x].son[!k]) t[t[x].son[!k]].fa=y;
	t[y].son[k]=t[x].son[!k],t[x].fa=t[y].fa,t[y].fa=x,t[x].son[!k]=y;
	t[x].pf=t[y].pf;
}
void splay(int x,int y){
	remove(x,y);
	while(t[x].fa!=y){
		if(t[t[x].fa].fa!=y)
			if(get(x)==get(t[x].fa)) rotate(t[x].fa);
			else rotate(x);
		rotate(x);
	}
}
int access(int x){
	int z;
	for (int y=0;x;y=x,x=t[x].pf){
		splay(x,0);
		t[t[x].son[1]].fa=0,t[t[x].son[1]].pf=x;
		t[x].son[1]=y,t[y].fa=x,t[y].pf=0,z=x;//z用来储存最后一次的根
	}
	return z;
}
int Get(int x){
	if(fa[x]==x) return x;
	return fa[x]=Get(fa[x]);
}
void makeroot(int x){
	access(x);
	splay(x,0);
	make(x);
}
void link(int x,int y){
	makeroot(y);
	int xx=Get(x),yy=Get(y);
	if(xx!=yy) fa[xx]=yy,t[y].pf=x;
}
int findans(int x,int y){
	if(Get(x)!=Get(y)) return -1;
 	int q=access(y);
	int p=access(x);
	return p;
}
int main(){
	freopen("arrival.in","r",stdin);
	freopen("arrival.out","w",stdout);
	scanf("%d %d",&n,&m);
	int i,j,x,y,kind,ans;
	for (i=1;i<=m;++i) scanf("%d",&root[i]);
	for (i=1;i<=n;++i) fa[i]=i;
	for (i=1;i<=n-m;++i){
		scanf("%d %d",&x,&y);
		link(x,y);
 	}
 	for (i=1;i<=m;++i) makeroot(root[i]);
	scanf("%d",&q);
	while(q--){
		scanf("%d%d%d",&kind,&x,&y);
		if(kind==1){
			if(Get(x)!=Get(y))link(x,y);
		}
		else{
			ans=findans(x,y);
			if(ans==-1) printf("orzorz\n");
			else printf("%d\n",ans);
		}
	}
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值