NKOI 夏令营训练赛1 到不了[B]

28 篇文章 0 订阅

到不了

Description

wywjk是好朋友。

今天他们在一起聊天,突然聊到了以前一起唱过的《到不了》。

“说到到不了,我给你讲一个故事吧。”

“嗯?”

“从前,神和凡人相爱了,愤怒的神王把他们关进了一个迷宫里,迷宫是由许多棵有根树组成的。神王每次把两个人扔进其中的某一棵有根树里面,两个相邻节点的距离为1,两人的每一步都只能从儿子走到父亲,不能从父亲走到儿子,他们约定,走到同一个节点相见,由于在迷宫里面行走十分消耗体力,他们决定找出那个使得他们走的总路程最少的节点,他们当然会算出那个节点了,可是神王有时候会把两棵有根树合并为一棵,这下就麻烦了。。。”

“唔。。。”

[已经了解树,森林的相关概念的同学请跳过下面一段]

树:由n个点,n-1条边组成的无向连通图。

父亲/儿子:把树的边距离定义为1,root是树的根,对于一棵树里面相邻的两个点u,v,到root的距离近的那个点是父亲,到root距离远的那个点是儿子

森林:由若干棵树组成的图

[简化版题目描述]

维护一个森林,支持连边操作和查询两点LCA操作

Input

第一行一个整数NM,代表森林里的节点总数和有根树的数目。

第二行M个整数,第i个整数ri代表第i棵有根树的根是编号为ri的节点

接下来N-M行,每行两个整数uv表示uv相邻

接下来一行一个整数Q,表示Q个事件发生了

接下来Q行,每行若干个整数,表示一个事件

如果第一个数op=1,接下来两个整数u,v,代表神王把u号节点所在的树和v号节点所在的树合并到一起(uv连了一条边),新的根为原来u号节点所在的树的根(如果u,v已经联通,忽略这个事件)

如果第一个数op=2,接下来两个整数uv,代表一次询问,当一个人在u号节点,一个人在v号节点,询问他们找到的那个节点的编号

Output

对于每一个询问(op=2的操作),输出一行一个整数,代表节点编号,如果uv不联通,输出orzorz

Sample Input

【样例1

2 2

1 2

2

1 1 2

2 1 2

【样例2

2 2

1 2

2

1 2 1

2 1 2

Sample Output

【样例1

1

【样例2

2

Hint

【数据范围】

对于30%的数据 ≤ ≤ 1000 ≤ ≤ 1000

对于100%的数据 ≤ ≤ 100000 ≤ ≤ 100000


这道题的LCA很容易想到,但是怎样插入一条边就成为了比较大的困难

在合并两棵树的时候我们要用到并查集,并用SIZE 数组来表示某节点所在的集合的大小,初值为1

题目中虽然说将u的根节点当作v的根节点,但是实际上交换u,v对结果是没有影响的,因此为了时间效率我们可以将小树合并在大树上,这样就减小了合并消耗的时间

对于操作1,用并查集维护每个点当前所在的树的编号,和这棵树现在“真正”的根是谁,对于操作2,我们要求出当LCA倍增数组是以某个点(VROOT)为根建立的时候,两点(uv)在以某个点(root)为根的意义下的LCA。求出uvroot两两之间的LCA,找出其中在以VROOT为根时,深度最大的那个LCA(讨论uvroot三点在以VROOT为根时的相对位置关系,不难证明其正确性)

#include<cstdio>
#include<cstdlib>
#include<iostream>
using namespace std;

const int maxn=100005;
const int logn=20;
int root[maxn],tt[maxn];//tt数组记录<span style="font-family:宋体;">某节点真正的根</span>
int f[maxn][logn+1],depth[maxn];
int last[maxn],NEXT[maxn<<1],END[maxn<<1];
int fa[maxn],size[maxn];
int n,m,tot,q,u,v,op,E;

void insert(int u,int v){
	NEXT[++tot]=last[u];
	END[tot]=v;
	last[u]=tot;
}

int finder(int x){return fa[x]==x?x:fa[x]=finder(fa[x]);}

void merge(int u,int v){
	u=finder(u);v=finder(v);
	if(u==v)return;
	fa[u]=v;
	size[v]+=size[u];
}

inline void _read(int &x){
    char t=getchar();bool sign=true;
    while(t<'0'||t>'9')
    {if(t=='-')sign=false;t=getchar();}
    for(x=0;t>='0'&&t<='9';t=getchar())x=x*10+t-'0';
    if(!sign)x=-x;
}

void DFS(int u){
	for(int k=1;k<=logn;++k) f[u][k]=f[f[u][k-1]][k-1];
	depth[u]=depth[f[u][0]]+1;
	for(int p=last[u],v;p;p=NEXT[p])
		if(END[p]!=f[u][0]){
			f[END[p]][0]=u;
			DFS(END[p]);
		}
}

int LCA(int u,int v){
	if(depth[u]<depth[v])swap(u,v);
	for(int k=logn;k>=0;--k)
		if(depth[f[u][k]]>=depth[v]) u=f[u][k];
	if(u==v)return u;
	for(int k=logn;k>=0;--k) 
		if(f[u][k]!=f[v][k]) u=f[u][k],v=f[v][k];
	return f[u][0];
}

void work1(){
	_read(u);_read(v);
	int fu=finder(u),fv=finder(v);
	int szu=size[fu],szv=size[fv];
	if(fu==fv) return ;
	insert(u,v);insert(v,u);<span style="font-family:宋体;">//以下为将较小树合并到较大树上去</span>
	if(szu>=szv){
		fa[fv]=fu,size[fu]+=size[fv];
		f[v][0]=u;
		DFS(v);
	}
	else{
		tt[fv]=tt[fu];
		fa[fu]=fv;size[fv]+=size[fu];
		f[u][0]=v;
		DFS(u);
	}
}

void work2(){
	_read(u);_read(v);
	int fu=finder(u),fv=finder(v);
	if(fu!=fv){
		puts("orzorz");
		return ;
	}
	int rot=tt[fu];
	int lca1=LCA(u,v),lca2=LCA(v,rot),lca3=LCA(u,rot);
	if(depth[lca1]<depth[lca2])lca1=lca2;
	if(depth[lca1]<depth[lca3])lca1=lca3;
	printf("%d\n",lca1);
}

int main(){
	int i;
	_read(n);_read(m);
	for(i=1;i<=m;i++)_read(root[i]);
	for(i=1;i<=n;i++)fa[i]=f[i][0]=i,size[i]=1;
	_read(E);
	for(i=1;i<=E;i++){
		_read(u);_read(v);
		insert(u,v);insert(v,u);
		merge(u,v);
	}
	for(i=1;i<=m;i++)DFS(root[i]);
	for(i=1;i<=n;i++)tt[i]=f[i][logn];
	_read(q);
	while(q--){
		_read(op);
		if(op==1)work1();
		else work2();
	}
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值