洛谷P1197 [JSOI2008]星球大战
题目描述
很久以前,在一个遥远的星系,一个黑暗的帝国靠着它的超级武器统治着整个星系。
某一天,凭着一个偶然的机遇,一支反抗军摧毁了帝国的超级武器,并攻下了星系中几乎所有的星球。这些星球通过特殊的以太隧道互相直接或间接地连接。
但好景不长,很快帝国又重新造出了他的超级武器。凭借这超级武器的力量,帝国开始有计划地摧毁反抗军占领的星球。由于星球的不断被摧毁,两个星球之间的通讯通道也开始不可靠起来。
现在,反抗军首领交给你一个任务:给出原来两个星球之间的以太隧道连通情况以及帝国打击的星球顺序,以尽量快的速度求出每一次打击之后反抗军占据的星球的连通块的个数。(如果两个星球可以通过现存的以太通道直接或间接地连通,则这两个星球在同一个连通块中)。
输入格式
输入文件第一行包含两个整数, n n n, m m m分别表示星球的数目和以太隧道的数目。星球用 0 ∼ n − 1 0∼n−1 0∼n−1 的整数编号。
接下来的 m m m行,每行包括两个整数 x , y x,y x,y,表示星球 x x x 和星球 y y y之间有 “以太” 隧道,可以直接通讯。
接下来的一行为一个整数 k k k ,表示将遭受攻击的星球的数目。
接下来的 k k k 行,每行有一个整数,按照顺序列出了帝国军的攻击目标。这 k k k 个数互不相同,且都在 0 0 0 到 n − 1 n−1 n−1 的范围内。
输出格式
第一行是开始时星球的连通块个数。接下来的 k k k 行,每行一个整数,表示经过该次打击后现存星球的连通块个数。
输入输出样例
输入 #1
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
1
2
3
3
说明/提示
【数据范围】
对于
100
%
100\%
100% 的数据,
1
≤
m
≤
2
×
1
0
5
1≤m≤2×10^5
1≤m≤2×105,
1
≤
n
≤
2
m
1≤n≤2m
1≤n≤2m,
x
≠
y
x≠y
x=y。
代码实现
import java.io.*;
public class P1197{
//邻接矩阵
public static int[] pre;
public static int[] to;
public static int[] next;
public static int[] h;
public static int top = -1;
public static int[] parent;
public static void main(String[] args) throws IOException {
StreamTokenizer in= new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
PrintWriter out= new PrintWriter(new OutputStreamWriter(System.out));
while(in.nextToken() !=StreamTokenizer.TT_EOF){
int n = (int)in.nval;
in.nextToken();
int m = (int)in.nval;
pre = new int[2*m+1]; //第x条边的端节点
to = new int[2*m+1]; //第x条边的尾节点
next = new int[2*m+1]; //与x节点相关联的上一条边的编号
h = new int[n]; //最后一条与x点相关联的边的编号
parent = new int[n];
for(int i=0;i<n;i++){ //数组初始化
h[i] = -1;
parent[i] = -1;
}
for(int i=1;i<=m;i++){ //画图:将每一条边进行记录
in.nextToken();
int x = (int)in.nval;
in.nextToken();
int y = (int)in.nval;
union(x,y);
union(y,x);
}
in.nextToken();
int k = (int)in.nval;
int[] ifdes = new int[n]; //标记i星球是否被摧毁
int[] des = new int[k]; //存放摧毁星球的序列
for(int i=0;i<k;i++){
in.nextToken();
int x = (int)in.nval;
ifdes[x] = -1; //代表被摧毁
des[i] = x;
}
int total = n-k; //连接块数 n(星球数)-k(摧毁星球数)
for(int i=0;i<2*m;i++){ //遍历所有的边,得全部摧毁后的连通块数
if(ifdes[pre[i]]!=-1 && ifdes[to[i]]!=-1){
int x_root = find(pre[i]);
int y_root = find(to[i]);
if(x_root!=y_root){
parent[x_root] = y_root;
total--;
}
}
}
int[] ans = new int[k+1]; //记录每摧毁一个星球后的连通块数
ans[k] = total;
//逆序遍历
/*逆向思维,按摧毁星球的顺序逆向连接,求连通块数*/
for(int i=k-1;i>=0;i--){
int star = des[i];
ifdes[star] = 0; //标记未摧毁
total++;
for(int j=h[star];j!=-1;j=next[j]){ //从最后一条与改星球有关的边一直向上找与该星球有关的边
if(ifdes[to[j]] == -1){
continue;
}else{
int x = find(star);
int y = find(to[j]);
if(x != y){
total--;
parent[x] = y;
}
}
}
ans[i] = total;
}
for(int i=0;i<=k;i++){
out.println(ans[i]);
}
out.flush();
}
}
public static int find(int x){ //寻找父节点
int root = x;
while (parent[root] != -1){
root = parent[root];
}
int j = x,i;
while (j != root){ //路径优化
i = parent[j];
parent[j] = root;
j = i;
}
return root;
}
public static void union(int x,int y){
pre[++top] = x;
to[top] = y;
next[top] = h[x];
h[x] = top;
}
}