[JSOI2008]最小生成树计数
[JSOI2008]最小生成树计数
Time Limit: 1 Sec Memory Limit: 162 MBSubmit: 408 Solved: 159
[Submit][Status][Discuss]
Description
Input
Output
Sample Input
1 2 1
1 3 1
1 4 1
2 3 2
2 4 1
3 4 1
Sample Output
注:我表示很无语!众多的大牛都是这样形容这道题目的:这道题很老,很水,希望来练练手,但是……
我是弱菜,这种题目肯定是不那么容易A掉的,这里只给出其他人的一些讲解,我也刚刚才学会这个知识的。
我们手算一些数据,可以发现这么一个规律:对于某一张图所有的最小生成树,权值为ci的边的个数是个定值。利用这个规律,我们提出一个算法:
先对图做一次最小生成树,确定各个权值ci的边的个数。然后按照kruskal的思路,由于边排序后相同权值的边是连续的一段,所以对于每一个权值ci,我们可以进行2^p的枚举(p为该权值的边的个数)得到其总的方案数ti,枚举完后,把这些边所连接的点变成连通的,再进行下一轮的枚举。最后按照乘法原理,把所有的ti乘起来,对31011取模,就是我们所要的答案。
由于数据范围保证了p<=10,所以2^p的枚举不会超时。
但是这个算法本身还有两个问题没有解决,一是为什么在枚举完某一权值的节点后,要把所有的边所连的点变成连通的?二是,为什么对于不同权值边的方案数,我们可以用乘法原理乘起来?其次,这个算法是基于我们发现的规律,那么我们发现的规律到底是否正确呢?下面的叙述将给出明确的回答:
首先,生成树有如下的性质:对于一颗生成树,我们加入任意一条边,必然会形成一个环。我们把环上的任意一条边删去,就可以得到一个新的生成树。
考虑一张图G,仅保留最小权的边c,得到新图G’,它不一定是连通的,可能是由几个连通分量组成,连通分量的点集分别为V1,V2……。我们所要证明的就是,对于图G的任意一个最小生成树,其V1,V2对应的子图G1,G2……必定是一颗树,并且都由边权为c的边组成。
我们用反证法来证明:假如对于图G的某一个最小生成树,其子图Gi不是一棵树,而是若干个连通块,那么由于图G是连通的,所以我们可以确定Gi中的连通块必然通过若干条其他边连起来。那么此时我们将子图中某条可以将两个连通块连接起来的边加入图中(这条边必定可以找到,因为图G’中,Gi为连通分量),这样图G的最小生成树就会形成一个环,并且这个环上必定有权值比c大的边(因为必定有边将Gi与其他子图连接起来),这时我们去掉权值比c大的边,可以得到一颗新的生成树,而且这颗生成树比最小生成树还要小,这样就产生了矛盾。
那么既然子图G1,G2……为一棵树,那么权值为c的边的个数必定为定值,并且在选择完所有的权值为c的边后,这些边所连的点必定是连通的。我们将这些由c组成的连通块缩成一个点,得到一张新图G’’,可以得知图G’’的最小边,即图G的次小边,仍满足上述结论。由此,我们可以得知,从小到大确定各个边的情况,是互不影响的,满足使用乘法原理的性质。
简言之,就是对于所有的边进行排序,对于权值为Ci的边如果在一棵最小生成树中需要Bi条,那么这个值是一个定值即所有的最小生成树都需要Bi条权值为Ci的边。这样在第一次构建生成树的时候就可以知道每一个权值的边需要多少条,最后根据乘法原理相乘即可。如果重合的边特别多的话就得用一个---矩阵。
不过这个代码需要注意个地方很多,大体思路明白了不一定能A掉。
type ji=record s,t,w:longint; end; jiji=record ss,tt:longint; end; var v:array[0..1005] of boolean; s:array[0..1005] of jiji; a:array[0..1005] of ji; f,f2:array[0..1005] of longint; ask:array[0..1005] of longint; e,now,i,m,n,ans,j:longint; procedure qs(s,t:longint); var i,j:longint; k,x:ji; begin i:=s; j:=t; x:=a[(i+j)>>1]; repeat while a[i].w<x.w do inc(i); while a[j].w>x.w do dec(j); if i<=j then begin k:=a[i]; a[i]:=a[j]; a[j]:=k; inc(i); dec(j); end; until i>j; if i<t then qs(i,t); if j>s then qs(s,j); end; function find(v:longint):longint; begin if f[v]=v then exit(v); f[v]:=find(f[v]); exit(f[v]); end; function find2(v:longint):longint; //注意点一,找父亲的时候不能采用find中的形式 begin if f2[v]=v then exit(v); exit(find2(f2[v])); //注意 end; procedure hb(i:longint); var js,jt:longint; begin js:=find(a[i].s); jt:=find(a[i].t); f[js]:=jt; end; function chazhao(es,et,kk:longint):longint; var k:longint; procedure dfs(b,x:longint); var fs,ft:longint; begin if x=kk then begin inc(k); exit; end; if b>et then exit; if (find2(a[b].s)<>find2(a[b].t))and(v[a[b].s])and(v[a[b].t]) then begin fs:=find2(a[b].s); ft:=find2(a[b].t); f2[fs]:=ft; dfs(b+1,x+1); f2[fs]:=fs; end; dfs(b+1,x); end; begin k:=0; dfs(es,0); exit(k); end; begin readln(n,m); fillchar(v,sizeof(v),0); fillchar(s,sizeof(s),0); fillchar(ask,sizeof(ask),0); for i:=1 to m do readln(a[i].s,a[i].t,a[i].w); for i:=1 to n do begin f2[i]:=i; f[i]:=i; end; qs(1,m); e:=0; now:=1; i:=1; s[1].ss:=1; s[1].tt:=1; while i<=m do begin if a[i].w=a[now].w then s[now].tt:=i else begin now:=i; s[i].ss:=i; s[i].tt:=i; end; inc(i); end; now:=1; ans:=1; for i:=1 to m do begin if a[i].w<>a[now].w then //这个地方注意一下,不能直接continue begin ans:=ans*chazhao(s[now].ss,s[now].tt,ask[now]) mod 31011; for j:=1 to n do f2[j]:=f[j]; now:=i; ask[now]:=0; end; if find(a[i].s)<>find(a[i].t) then begin v[a[i].s]:=true; v[a[i].t]:=true; hb(i); inc(e); inc(ask[now]); end; if e=n-1 then break; end; ans:=ans*chazhao(s[now].ss,s[now].tt,ask[now]) mod 31011; if e<>n-1 then writeln(0) else writeln(ans mod 31011); end.