BZOJ_1016_[JSOI2008]_最小生成树计数_(dfs+乘法原理)

描述


http://www.lydsy.com/JudgeOnline/problem.php?id=1016

给出一张图,其中具有相同权值的边的数目不超过10,求最小生成树的个数.

 

分析


生成树的计数有一个什么什么算法...

我真的企图研究了...但是智商捉急的我实在看不懂论文...

所以最后还是写了暴力...

当然暴力也要靠正确的姿势的.

首先来看一个结论:

同一张图的所有最小生成树中,边权值相同的边的数目是一定的.

也就是说,假如某一张图的某一棵最小生成树由边权值为1,1,2,2,2,3的边组成,那么这张图的所有其他最小生成树也都由六条权值分别为1,1,2,2,2,3的边组成.

至于证明,我们可以用数学归纳法.

1.以上结论对与n=2的情况显然成立.

2.假设对于n=k的情况以上结论成立即k个节点的最小生成树满足相同权值的边的数目一定.那么对于n=k+1的情况,需要加一条边将第k+1个点连起来,因为生成树是最小的,所以所加的边的权值是一定的,而之前的k-1条边(连接之前的k个点的边)各权值的边的数目也是一定的,所以最终各权值的边的数目是一定的.

 

有了这样的一个结论,又因为相同权值的边的数目不超过10,所以我们就可以利用暴力dfs+乘法原理,统计每一种权值的边的选择的方案数,然后相乘即可.

 

 

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 
 4 const int maxn=100+5,maxm=1000+5,mod=31011;
 5 struct edge{
 6     int u,v,w;
 7     edge(){}
 8     edge(int u,int v,int w):u(u),v(v),w(w){}
 9     bool operator < (const edge &a) const { return w<a.w; }
10 }g[maxm];
11 int n,m,cnt,sum,ans=1;
12 int a[maxm],b[maxm],ect[maxm],f[maxn];
13 inline int read(int &x){x=0;int k=1;char c;for(c=getchar();c<'0'||c>'9';c=getchar())if(c=='-')k=-1;for(;c>='0'&&c<='9';c=getchar())x=x*10+c-'0';return x*=k;}
14 inline int find(int x){return x==f[x]?x:find(f[x]);}
15 void dfs(int s,int t,int rem){
16     if(rem==0){
17         sum++;
18         return;
19     }
20     for(int i=s;i-1+rem<=t;i++){
21         int fv=find(g[i].v),fu=find(g[i].u);
22         if(fu!=fv){
23             int F=f[fu];
24             f[fu]=fv;
25             dfs(i+1,t,rem-1);
26             f[fu]=F;
27         }
28     }
29 }
30 inline void solve(){
31     int i,j;
32     for(i=1,j=1;i<=m&&j<n;i++){
33         int fu=find(g[i].u),fv=find(g[i].v);
34         if(fu!=fv){
35             f[fu]=fv;
36             ect[b[i]]++;
37             j++;
38         }
39     }
40     if(j<n){
41         puts("0");
42         return;
43     }
44     for(int i=1;i<=n;i++) f[i]=i;
45     for(i=1;i<=cnt;i++){
46         sum=0;
47         dfs(a[i],a[i+1]-1,ect[i]);
48         for(int j=a[i];j<a[i+1];j++){
49             int fu=find(g[j].u),fv=find(g[j].v);
50             if(fu!=fv)     f[fu]=fv;
51         }
52         ans=(ans*sum)%mod;
53     }
54     printf("%d\n",ans);
55 }
56 inline void init(){
57     read(n); read(m);
58     for(int i=1;i<=n;i++) f[i]=i;
59     for(int i=1,u,v,w;i<=m;i++){
60         read(u); read(v); read(w);
61         g[i]=edge(u,v,w);
62     }
63     sort(g+1,g+1+m);
64     for(int i=1;i<=m;i++){
65         if(g[i].w!=g[i-1].w) a[++cnt]=i;
66         b[i]=cnt;
67     }
68     a[cnt+1]=m+1;
69 }
70 int main(){
71     init();
72     solve();
73     return 0;
74 }
View Code

 

1016: [JSOI2008]最小生成树计数

Time Limit: 1 Sec  Memory Limit: 162 MB
Submit: 4666  Solved: 1890
[Submit][Status][Discuss]

Description

  现在给出了一个简单无向加权图。你不满足于求出这个图的最小生成树,而希望知道这个图中有多少个不同的
最小生成树。(如果两颗最小生成树中至少有一条边不同,则这两个最小生成树就是不同的)。由于不同的最小生
成树可能很多,所以你只需要输出方案数对31011的模就可以了。

Input

  第一行包含两个数,n和m,其中1<=n<=100; 1<=m<=1000; 表示该无向图的节点数和边数。每个节点用1~n的整
数编号。接下来的m行,每行包含两个整数:a, b, c,表示节点a, b之间的边的权值为c,其中1<=c<=1,000,000,0
00。数据保证不会出现自回边和重边。注意:具有相同权值的边不会超过10条。

Output

  输出不同的最小生成树有多少个。你只需要输出数量对31011的模就可以了。

Sample Input

4 6
1 2 1
1 3 1
1 4 1
2 3 2
2 4 1
3 4 1

Sample Output

8

HINT

Source

posted @ 2016-06-24 23:05  晴歌。  阅读(...)  评论(... 编辑 收藏