Description
你应该知道无向图的连通块的数量,你应该知道如何求连通块的数量。当你兴奋与你的成就时,破坏王Alice拆掉了图中的边。当她发现,每删去一条边,你都会记下边的编号,同时告诉她当前连通块的个数。
然而,对边编号简直就是个悲剧,因为Alice为了刁难你,拆掉编号从l到r的边,当然你需要做的事情就是求连通块的个数。如果你答对了,Alice会把拆掉的边装好,迚行下一次破坏。如果你无法完成这个任务,Alice会彻底毁了你的图。
进行完足够多次之后,Alice觉得无聊,就玩去了,而你却需要继续做第三题。
Input
第一行两个整数n,m,表示点数和边数。
之后m行每行两个整数x,y,表示x与y之间有无向边。(按读入顺序给边编号,编号从1开始)
一行一个整数k,表示Alice的破坏次数。
之后k行,每行两个整数l,r。
Output
k行,每行一个整数。
Sample Input
6 5
1 2
5 4
2 3
3 1
3 6
6
1 3
2 5
1 5
5 5
2 4
3 3
Sample Output
4
5
6
3
4
2
Data Constraint
对于30%的数据,n<=100,k<=10
对于60%的数据,k<=1000
对于100%的数据,n<=500,m<=10000,k<=20000,1<=l<=r<=m
分析:显然就是求去掉一个区间后的连通块数,就是把其他的边并起来,求集合数。
我们发现每个答案,都是从前1……l-1到r+1……n这样的两个集合并起来,我们可以先预处理出
每一个 first[i] 为一个并查集,记录只添加前 i 条边时,所有节点的联通情况。
每一个 last[i] 也是一个并查集,记录只添加 i—m 条边时,所有节点的联通情况。
每次就是把first[l-1]和last[r+1]合并起来。
问题是,怎样把两个集合并起来呢?(考试时想了好久)
我们开一个数组h表示合并后的集合:
(1)把第一个集合的数据加入到h中。
(2)判断其中一个点i在两个集合中是否属于同一祖先,如果不属于,则可以把这两个祖先合并起来。
(3)得出最后的集合,判断集合数。
代码:
type
arr=array [1..501] of longint;
var
first,last:array [0..10001] of arr;
g:arr;
x,y:array [1..10001] of longint;
n,k,i,j,l,r,x1,y1,ans,m,t:longint;
function find(x:longint;var p:arr):longint;
var y,root,w:longint;
begin
y:=x;
while p[y]>0 do
y:=p[y];
root:=y;
y:=x;
while p[y]>0 do
begin
w:=p[y];
p[y]:=root;
y:=w;
end;
find:=root;
end;
procedure union(x,y:longint;var p:arr);
var s,t:longint;
begin
s:=find(x,p);
t:=find(y,p);
if s<>t then
p[s]:=t;
end;
begin
assign(input,'connect.in');
assign(output,'connect.out');
reset(input);
rewrite(output);
readln(n,m);
for i:=1 to m do
readln(x[i],y[i]);
for i:=1 to m do
begin
first[i]:=first[i-1];
union(x[i],y[i],first[i]);
end;
for i:=m downto 1 do
begin
last[i]:=last[i+1];
union(x[i],y[i],last[i]);
end;
readln(k);
for i:=1 to k do
begin
readln(l,r);
ans:=0;
l:=l-1; r:=r+1;
g:=first[l];
for j:=1 to n do
begin
x1:=find(j,g);
y1:=find(j,last[r]);
if x1<>y1 then
union(x1,y1,g);
end;
for j:=1 to n do
if g[j]=0 then inc(ans);
writeln(ans);
end;
close(input);
close(output);
end.