您的位置:首页 > 其它

[jzoj]3875. 【NOIP2014八校联考第4场第2试10.20】星球联盟(alliance)(图论题,构树+缩点+LCA+并查集)

2017-08-21 13:08 555 查看
Description

在遥远的S星系中一共有N个星球,编号为1…N。其中的一些星球决定组成联盟,以方便相互间的交流。

但是,组成联盟的首要条件就是交通条件。初始时,在这N个星球间有M条太空隧道。每条太空隧道连接两个星球,使得它们能够相互到达。若两个星球属于同一个联盟,则必须存在一条环形线路经过这两个星球,即两个星球间存在两条没有公共隧道的路径。

为了壮大联盟的队伍,这些星球将建设P条新的太空隧道。这P条新隧道将按顺序依次建成。一条新轨道建成后,可能会使一些星球属于同一个联盟。你的任务是计算出,在一条新隧道建设完毕后,判断这条新轨道连接的两个星球是否属于同一个联盟,如果属于同一个联盟就计算出这个联盟中有多少个星球。

Input

第1行三个整数N,M和P,分别表示总星球数,初始时太空隧道的数目和即将建设的轨道数目。

第2至第M+1行,每行两个整数,表示初始时的每条太空隧道连接的两个星球编号。

第M+2行至第M+P+1行,每行两个整数,表示新建的太空隧道连接的两个星球编号。这些太空隧道按照输入的顺序依次建成。

Output

输出共P行。如果这条新的太空隧道连接的两个星球属于同一个联盟,就输出一个整数,表示这两个星球所在联盟的星球数。如果这条新的太空隧道连接的两个星球不属于同一个联盟,就输出”No”(不含引号)。

Sample Input

输入1:

3 2 1

1 2

1 3

2 3

输入2:

5 3 4

1 2

4 3

4 5

2 3

1 3

4 5

2 4

Sample Output

输出1:

3

输出2:

No

3

2

5

Data Constraint

对于10%的数据有1≤N,M,P≤100;

对于40%的数据有1≤N,M,P≤2000;

对于100%的数据有1≤N,M,P≤200000。

Hint

Problem

给定N条原始边,M条新加入的边,每次新连一条边,判断边所连的两点是否处于同一联通分量里,如果是输出这个联通分量的最大大小,如果不是,输出no.

Preface

这是一道非常好的图论题.

Solution

1.强连通分量,正难则反

首先,第一眼,既然是强连通分量,那肯定会想到Tarjan,可是这题每次跑Tarjan显然是过不了的.

那么,我们转化思路,题目既然让我们求环的大小,而求环大小肯定会超时,那么是否可以建一个图,让它既不是环,效率又很高呢??

没错,这样一种东西叫做树.

2.树的应用

在树上维护连通性?想到了什么,没错,并查集.

其实这一题我一开始想到的就是并查集,但是由于太naive,一分都没拿到.

想象一下,如果当前我要新加入一条边,在树上的任何一条新加入的边,都会使其形成环,前提是一颗树,而不是森林.

那么,我们每次形成一个环,我可以把它压成一个点,我们可以不使他真的成环,但是我们可以用并查集维护子孙关系,统计一个点它所在环的大小,下次如果再找到这个点,直接用它的环的大小即可.

3、并查集

关键就是如何维护子孙关系,还有环的大小,以及下次询问时,如何找到前面计算的结果.

其实就打一个并查集就好了!–因为我们可以联想到LCA

4、LCA–缩点

我们打并查集时,每次连两个点,就把它们所在环都给连起来.

简单说来,就是把一个环中所有的点都连向LCA,一开始这些点都连向自己.

如果新加入一条边,我们算出这两个点到LCA上所有环的大小,然后就是询问的答案了.

询问完答案之后,我们再把这两个点以及到LCA上的所有点都连向LCA.

这样下一次我们并查集时就可以直接get它的父亲,也就是LCA,然后就可以找到前面计算的结果了.

5、构树

这题还要注意的一个地方就是,我们如何构树?

如果给我们一堆无向边,我们是无法直接确定树的,所以我们可以先把M+P条边一起做,然后直接构出一个树,我们在这个树上再搞,既可以保证答案的正确性,又可以方便操作.

注意,不能只构一遍树,因为有可能是一个森林.

6、时间复杂度

最后,其实打LCA就是为了缩点,一开始我没有想到缩点,于是只有TLE50,后来想到并查集+路径压缩+缩点,就可以效率飞起了.

时间复杂度不是很好证,但是感性的理解一下,并查集那种神奇的时间复杂度,是不会超时的.

毕竟n,m,p才200000.

#include <iostream>
#include <cstdio>
#include <cstring>

#define Maxn 400100

using namespace std;

int n,m,p,i,j,x,y,xx,yy,w,ans,len,tot; //ans记录环大小,len记录有多少个点被更改

int sx[Maxn],sy[Maxn],fa[Maxn],father[Maxn],grade[Maxn],sum[Maxn],deep[Maxn],d[Maxn],g[Maxn],dep[Maxn];
//fa[i]记录父亲 ,grade[i]记录标号 ,sum[i]表示以grade[i]为编号的环大小. father[i] 用于构图,可以路径压缩!

int tov[Maxn],next[Maxn],last[Maxn];

bool flag[Maxn];

int getstep(int x)
{
if (father[x]==0) return x;
father[x]=getstep(father[x]);
return father[x];
}

int get(int x)
{
if (fa[x]==0) return x;
return get(fa[x]);
}

int lca(int x,int y)
{
if ((deep[x]==deep[y]) && (x==y)) return x;

if (deep[x]>deep[y]) return lca(fa[x],y); else
if (deep[y]>deep[x]) return lca(x,fa[y]); else
return lca(fa[x],fa[y]);
}
void insert(int x,int y)
{
tov[++tot]=y;
next[tot]=last[x];
last[x]=tot;
}
void dfsdeep(int x)
{
int k=last[x];
while (k>0)
{
if (flag[tov[k]])
{
fa[tov[k]]=x;
flag[tov[k]]=false;
d[++len]=tov[k], deep[tov[k]]=deep[x]+1, dfsdeep(tov[k]);
}
k=next[k];
}
}

int getfather(int x)
{
if (g[x]==x) return x;
g[x]=getfather(g[x]);
return g[x];
}

void dfs(int x,int y)
{
xx=getfather(x);

if (flag[ grade[ xx ] ])
{
flag[ grade[ xx ] ]=false;

d[++len] = grade[ xx ], ans+=sum[ grade[ xx ] ], grade[ xx ]=grade[ y ];
}
if (xx==y) return;

dfs(fa[x],y);

fa[x]=y;
g[x]=y;
}

int main()
{
scanf("%d%d%d",&n,&m,&p);

for (i=1;i<=m+p;i++)
{
scanf("%d%d",&sx[i],&sy[i]);

xx=getstep(sx[i]),yy=getstep(sy[i]);

if (xx!=yy)
{
if (dep[xx]<dep[yy]) father[xx]=yy; else father[yy]=xx;
if (dep[xx]==dep[yy]) dep[xx]++;
insert(sx[i],sy[i]), insert(sy[i],sx[i]);
}
}

memset(flag,1,sizeof(flag));
for (i=1;i<=n;i++)
if (flag[i]) flag[i]=false, deep[i]=1, dfsdeep(i);
for (i=1;i<=n;i++)
grade[i]=i, g[i]=i, sum[i]=1;

memset(flag,1,sizeof(flag));
memset(father,0,sizeof(father));
memset(dep,0,sizeof(dep));

for (i=1;i<=m+p;i++)
{
x=sx[i],y=sy[i];

xx=getstep(x),yy=getstep(y),len=0;

if (xx!=yy)
{
if (dep[xx]<dep[yy]) father[xx]=yy; else father[yy]=xx;
if (dep[xx]==dep[yy]) dep[xx]++;

if (i>m) printf("No\n");
}
else
{
xx=getfather(x), yy=getfather(y);

w=getfather(lca(xx,yy)),ans=0,len=0;

dfs(xx,w);
dfs(yy,w);

sum[w]=ans;

if (i>m) printf("%d\n",ans);
}

for (j=1;j<=len;j++)
flag[d[j]]=true;
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐