NKOI 夏令营训练赛1 到不了[B]
2016-07-17 11:39
309 查看
到不了
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。
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%的数据 1 ≤ N ≤ 1000
1 ≤ Q ≤ 1000
对于100%的数据 1 ≤ N ≤ 100000
1 ≤ Q ≤ 100000
这道题的LCA很容易想到,但是怎样插入一条边就成为了比较大的困难
在合并两棵树的时候我们要用到并查集,并用SIZE 数组来表示某节点所在的集合的大小,初值为1
题目中虽然说将u的根节点当作v的根节点,但是实际上交换u,v对结果是没有影响的,因此为了时间效率我们可以将小树合并在大树上,这样就减小了合并消耗的时间
对于操作1,用并查集维护每个点当前所在的树的编号,和这棵树现在“真正”的根是谁,对于操作2,我们要求出当LCA倍增数组是以某个点(VROOT)为根建立的时候,两点(u,v)在以某个点(root)为根的意义下的LCA。求出u,v,root两两之间的LCA,找出其中在以VROOT为根时,深度最大的那个LCA(讨论u,v,root三点在以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();
}
}
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。
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%的数据 1 ≤ N ≤ 1000
1 ≤ Q ≤ 1000
对于100%的数据 1 ≤ N ≤ 100000
1 ≤ Q ≤ 100000
这道题的LCA很容易想到,但是怎样插入一条边就成为了比较大的困难
在合并两棵树的时候我们要用到并查集,并用SIZE 数组来表示某节点所在的集合的大小,初值为1
题目中虽然说将u的根节点当作v的根节点,但是实际上交换u,v对结果是没有影响的,因此为了时间效率我们可以将小树合并在大树上,这样就减小了合并消耗的时间
对于操作1,用并查集维护每个点当前所在的树的编号,和这棵树现在“真正”的根是谁,对于操作2,我们要求出当LCA倍增数组是以某个点(VROOT)为根建立的时候,两点(u,v)在以某个点(root)为根的意义下的LCA。求出u,v,root两两之间的LCA,找出其中在以VROOT为根时,深度最大的那个LCA(讨论u,v,root三点在以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();
}
}
相关文章推荐
- JAVA之NIO按行读写大文件,完美解决中文乱码问题
- TCP通信(一)——同步连接
- Java 多态的详解
- 创业14
- AtCoder Grand Contest 001 C Shorten Diameter 树的直径知识
- 数据库——事务基础
- org.msgpack.core.MessagePacker
- 用户(user)和用户组(group)管理
- 个人Linux发行版SwairOS
- Python 小甲鱼教程 课后练习30 番外篇_需要谨记!!!
- 你未必知道的CSS故事:揭开leading的面纱
- C++转换构造函数与类型转换构造函数
- LinkedHashMap类源码解析
- MySQL online ddl
- [Android]使用RecyclerView替代ListView(一)
- TCP中select函数的理解
- PLSQL Developer实现数据库间表结构和数据对比和同步
- nginx 499错误
- 创业12
- 第15章习题解答(二)——《x86汇编语言:从实模式到保护模式》读书笔记41