题目描述
很久以前,在一个遥远的星系,一个黑暗的帝国靠着它的超级武器统治者整个星系。某一天,凭着一个偶然的机遇,一支反抗军摧毁了帝国的超级武器,并攻下了星系中几乎所有的星球。这些星球通过特殊的以太隧道互相直接或间接地连接。
但好景不长,很快帝国又重新造出了他的超级武器。凭借这超级武器的力量,帝国开始有计划地摧毁反抗军占领的星球。由于星球的不断被摧毁,两个星球之间的通讯通道也开始不可靠起来。现在,反抗军首领交给你一个任务:给出原来两个星球之间的以太隧道连通情况以及帝国打击的星球顺序,以尽量快的速度求出每一次打击之后反抗军占据的星球的连通块的个数。(如果两个星球可以通过现存的以太通道直接或间接地连通,则这两个星球在同一个连通块中)。
输入输出格式
输入格式:输入文件第一行包含两个整数,N (1 <= N <= 2M) 和M (1 <= M <= 200,000),分别表示星球的数目和以太隧道的数目。星球用0~N-1的整数编号。
接下来的M行,每行包括两个整数X, Y,其中(0<=X<>Y<N),表示星球X和星球Y之间有以太隧道。注意所有的以太隧道都是双向的。
接下来一行是一个整数K,表示帝国计划打击的星球个数。
接下来的K行每行一个整数X,满足0<=X<N,表示帝国计划打击的星球编号。帝国总是按输入的顺序依次摧毁星球的。
输出格式:输出文件的第一行是开始时星球的连通块个数。
接下来的K行,每行一个整数,表示经过该次打击后现存星球的连通块个数。
输入输出样例
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
1
1
1
2
3
3
说明
[JSOI2008]
Solution:
本题很容易想到并查集,但是若顺序去加边,由于路径压缩了每次删点就很难统计直接与该点相连的点并且判断连通块是否的变化。
正着不行就反向试试,此时考虑离线,逆向去加点并统计形成的连通块个数。
那么每条边的修改顺序就是它所连两点中最早被修改的时间,初始连通块个数为$tot=n$(即$n$个点),再按修改时间往前倒推去加边,每当形成新的连通块则连通块个数$tot-1$,则第$i$次修改时对应的答案为$tot-(k-i)$,即当前的连通块个数减去已经被删去的点个数(注意是逆序,$i+1——k$为$i$之前的操作,必定已经删去了$k-i$个点),最后再倒序输出答案,就$OK$了。
(貌似有点绕,看下代码就明了了~)
代码:
#include<bits/stdc++.h> #define il inline #define ll long long using namespace std; const int N=400005; int n,m,k,fa[N],tot,pos[N],ans[N]; struct edge{ int x,y,id; bool operator <(edge a){return id<a.id;} }a[N]; il int gi(){ int a=0;char x=getchar();bool f=0; while((x<'0'||x>'9')&&x!='-')x=getchar(); if(x=='-')x=getchar(),f=1; while(x>='0'&&x<='9')a=a*10+x-48,x=getchar(); return f?-a:a; } il int find(int x){if(x==fa[x])return x;return fa[x]=find(fa[x]);} il void merge(int x,int y){x=find(x),y=find(y);if(x!=y)fa[x]=y,tot--;} int main() { n=gi(),m=gi(),tot=n; for(int i=0;i<=n;i++)fa[i]=i; for(int i=1;i<=m;i++){ a[i].x=gi(),a[i].y=gi(),a[i].id=i; } k=gi();int op; for(int i=k;i>=1;i--)op=gi(),pos[op]=i; for(int i=1;i<=m;i++)a[i].id=max(pos[a[i].x],pos[a[i].y]); sort(a+1,a+m+1); op=1; for(int i=0;i<=k;i++){ while(a[op].id==i){merge(a[op].x,a[op].y);op++;} ans[i]=tot-(k-i); } while(k!=-1)printf("%d\n",ans[k--]); return 0; }