[BZOJ 1015][JSOI2008]星球大战starwar

Description

很久以前,在一个遥远的星系,一个黑暗的帝国靠着它的超级武器统治者整个星系。某一天,凭着一个偶然的机遇,一支反抗军摧毁了帝国的超级武器,并攻下了星系中几乎所有的星球。这些星球通过特殊的以太隧道互相直接或间接地连接。 但好景不长,很快帝国又重新造出了他的超级武器。凭借这超级武器的力量,帝国开始有计划地摧毁反抗军占领的星球。由于星球的不断被摧毁,两个星球之间的通讯通道也开始不可靠起来。现在,反抗军首领交给你一个任务:给出原来两个星球之间的以太隧道连通情况以及帝国打击的星球顺序,以尽量快的速度求出每一次打击之后反抗军占据的星球的连通快的个数。(如果两个星球可以通过现存的以太通道直接或间接地连通,则这两个星球在同一个连通块中)。

Input

输入文件第一行包含两个整数,N (1 <= N <= 2M) 和M (1 <= M <= 200,000),分别表示星球的数目和以太隧道的数目。星球用0~N-1的整数编号。接下来的M行,每行包括两个整数X, Y,其中(0<=X<>Y

Output

输出文件的第一行是开始时星球的连通块个数。接下来的N行,每行一个整数,表示经过该次打击后现存星球的连通块个数。

Sample Input

8 13
0 1
1 6
6 5
5 0
0 6
1 2
2 3
3 4
4 5
7 1
7 2
7 6
3 6
5
1
6
3
5
7

Sample Output

1
1
1
2
3
3

HINT

Source

 

被神犇忽悠是水题才做,明眼人都能看出来这题倒着做并查集,不过点太多了,卧槽。。。用邻接表,我最讨厌邻接表了,题目思路不难,就是邻接表坑死我一个多小时,fuck,恶补邻接表去

#include <stdio.h>
#define MAXN 400020
int f[MAXN],cnt=0;
int head[MAXN],key[MAXN],next[MAXN],last[MAXN],kill[MAXN],x[MAXN],y[MAXN],ans[MAXN]; 
//kill[i]=第i次打击的边编号,x[i]=第i条边的起点,y[i]=第i条边的终点,ans[i]=经过i次加边后的连通度(连通分量个数)
bool used[MAXN]; //used[i]=true表示第i条边用过了,false表示第i条边没有用过
void addLine(int x,int y) //建边操作
{
	cnt++;
	last[cnt]=x;
	next[cnt]=head[y];
	head[y]=cnt;
}
void makeSet(int x) //并查集初始化
{
	f[x]=x;
}
int findSet(int x) //并查集查找,路径压缩优化
{
	if(f[x]==x) return x;
	return f[x]=findSet(f[x]);
}
void Union(int x,int y)
{
	int rootx=findSet(x);
	int rooty=findSet(y);
	f[rootx]=f[rooty];
}
int main()
{
	int i,j,k,n,m,t;
	scanf("%d%d",&n,&m);
	for(i=1;i<=n;i++) //并查集初始化
		makeSet(i);
	for(i=1;i<=m;i++)
	{
		scanf("%d%d",&x[i],&y[i]);
		//建无向边
		addLine(x[i],y[i]);
		addLine(y[i],x[i]);
	}
	scanf("%d",&k);
	t=n-k;
	for(i=1;i<=k;i++)
	{
		scanf("%d",&kill[k-i+1]); //这里打击边变成了添加边,第i个要打击的边=第k-i+1个要加的边,逆向思维
		used[kill[k-i+1]]=true;
	}
	//倒着做并查集,从无边到有边
	//第一次,求出未加边前的连通度
	for(i=1;i<=m;i++)
	{
		if((used[x[i]]==false)&&(used[y[i]]==false)&&findSet(x[i])!=findSet(y[i]))
		{
			Union(x[i],y[i]);
			t--; //连通两个点,就少了一条边......
		}
	}
	ans[0]=t; //经过0次加边后的连通度=t
	for(i=1;i<=k;i++)
	{
		//增加一条边,连通度+1
		t++;
		used[kill[i]]=false;
		j=head[kill[i]];
		while(j!=0)
		{
			if((used[last[j]]==false)&&findSet(last[j])!=findSet(kill[i])) //如果新增加的点对应边可以使用,且当前遍历的点与新增加的点可以连通,但没有连通
			{
				Union(kill[i],last[j]);
				t--; //同上,连通两个点,就少了一条边,连通度-1
			}
			j=next[j]; //遍历与边j相连的下一条边
		}
		ans[i]=t; //记录加了第i个边后的连通度
	}
	for(i=k;i>=0;i--)
		printf("%d\n",ans[i]);
	return 0;
}


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值