JZOJ5883. 【NOIP2018模拟A组9.25】到不了

Description

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

Input

第一行一个整数 N,M,代表森林里的节点总数和有根树的数目。
第二行 M 个整数,第 i 个整数 ri 代表第 i 棵有根树的根是编号为 ri 的节点
接下来 N-M 行,每行两个整数 u,v 表示 u 和 v 相邻
接下来一行一个整数 Q,表示 Q 个事件发生了
接下来 Q 行,每行若干个整数,表示一个事件
如果第一个数 op=1,接下来两个整数 u,v,代表神王把 u 号节点所在的树和 v 号节点所在的树 合并到一起(即 u 到 v 连了一条边),新的根为原来 u 号节点所在的树的根(如果 u,v 已经联通, 忽略这个事件)。
如果第一个数 op=2,接下来两个整数 u,v,代表一次询问,当一个人在 u 号节点,一个人 在 v 号节点,询问他们找到的那个节点的编号

Output

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

Data Constraint

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

题解

一看就可以知道是lct,于是就切了。
可惜这题还有noip做法。
假设知道了两个点(u,v)在某个点为根的lca,
那么分情况讨论一下,就可以知道,这两个点(u,v)在root为根的时候的lca就是这三个点两两组合求出lca中深度最大的那个。
而这题并没有强制在线,
先用并查集维护一下,就可以建出一棵最后的树,
然后再这棵树上面,随便找点,预处理好倍增数组。
再扫一遍操作,同样用并查集维护每个点这个时候的根,
直接求lca。

code

#include <queue>
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <string.h>
#include <cmath>
#define ll long long
#define N 1000003
#define P putchar
#define G getchar
using namespace std;
char ch;
void read(int &n)
{
	n=0;
	ch=G();
	while((ch<'0' || ch>'9') && ch!='-')ch=G();
	ll w=1;
	if(ch=='-')w=-1,ch=G();
	while('0'<=ch && ch<='9')n=(n<<3)+(n<<1)+ch-'0',ch=G();
	n*=w;
}

int max(int a,int b){return a>b?a:b;}
int min(int a,int b){return a<b?a:b;}
ll abs(ll x){return x<0?-x:x;}
ll sqr(ll x){return x*x;}
void write(ll x){if(x>9) write(x/10);P(x%10+'0');}

struct node
{
	int op,x,y;
}cz[N];

int bel[N],is[N];
int f[20][N],dep[N],root[N];
int n,nn,m,q,x,y;
int nxt[N*2],to[N*2],lst[N],tot,op,id,ans,t;

int get(int x){return is[x]=(is[x]==x?x:get(is[x]));}

void ins(int x,int y)
{
	nxt[++tot]=lst[x];
	to[tot]=y;
	lst[x]=tot;
}

void dfs(int x,int top)
{
	int fa=f[0][x];bel[x]=top;
	for(int i=lst[x];i;i=nxt[i])
		if(to[i]!=fa)f[0][to[i]]=x,dfs(to[i],top);
}

void dfs_(int x)
{
	int fa=f[0][x];
	dep[x]=dep[fa]+1;
	for(int i=lst[x];i;i=nxt[i])
		if(to[i]!=fa)f[0][to[i]]=x,dfs_(to[i]);
}

int lca(int x,int y)
{
    if(dep[x]>dep[y])swap(x,y);
    for(int j=16;j>=0;j--)
        if(dep[x]<=dep[f[j][y]])y=f[j][y];
    if(x==y)return x;
    for(int j=16;j>=0;j--)
        if(f[j][x]^f[j][y])x=f[j][x],y=f[j][y];
    return f[0][x];
}

int lca_(int x,int y)
{
	if(dep[x]<dep[y])swap(x,y);
	for(;dep[x]>dep[y];x=f[0][x]);
	for(;x!=y;x=f[0][x],y=f[0][y]);
	return x;
}

int main()
{
	freopen("arrival.in","r",stdin);
	freopen("arrival.out","w",stdout);
	
	read(n);read(m);
	for(int i=1;i<=m;i++)read(root[i]);
	for(int i=1;i<=n-m;i++)
		read(x),read(y),ins(x,y),ins(y,x);
	
	for(int i=1;i<=m;i++)dfs(root[i],root[i]);
	memcpy(is,bel,sizeof(is));
	
	read(q);
	for(int i=1;i<=q;i++)
	{
		read(cz[i].op);read(cz[i].x);read(cz[i].y);
		op=cz[i].op;x=cz[i].x;y=cz[i].y;
		if(op==1)
		{
			if(get(x)==get(y))continue;
			is[get(y)]=get(x);
			ins(x,y);ins(y,x);
		}
	}
	
	memset(f[0],0,sizeof(f[0]));
	for(int i=1;i<=m;i++)
		if(get(root[i])==root[i])dfs_(root[i]);
	
	for(int j=1;j<17;j++)
		for(int i=1;i<=n;i++)
			f[j][i]=f[j-1][f[j-1][i]];
	
	memcpy(is,bel,sizeof(is));
	
	for(int i=1;i<=q;i++)
	{
		op=cz[i].op;x=cz[i].x;y=cz[i].y;
		if(op==1)
		{
			if(get(x)==get(y))continue;
			is[get(y)]=get(x);
		}
		else
		{
			if(get(x)^get(y))
			{
				puts("orzorz");
				continue;
			}
			id=get(x);
			ans=lca(x,y);
			t=lca(x,id);
			if(dep[t]>dep[ans])ans=t;
			t=lca(y,id);
			if(dep[t]>dep[ans])ans=t;
			write(ans);P('\n');
		}
	}
	
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值