【BZOJ】【1016】【JSOI2008】最小生成树计数

Kruskal/并查集+枚举


  唉我还是too naive,orz Hzwer

  一开始我是想:最小生成树删掉一条边,再加上一条边仍是最小生成树,那么这两条边权值必须相等,但我也可以去掉两条权值为1和3的,再加上权值为2和2的,不也满足题意吗?事实上,如果这样的话……最小生成树应该是1和2,而不是1和3或2和2!!!

  所以呢?所以对于一个图来说,最小生成树有几条边权为多少的边,都是固定的!所以我们可以做一遍Kruskal找出这些边权,以及每种边权出现的次数。然后,对于每种边权,比方说出现了$v_i$次,那么可以替换的一定是形成环的!且这种边权的选取方案对其他边权的没有影响,我就可以$2^{v_i}$枚举每条边是否选取,暴力找出这$v_i$条边的可行方案数,这个复杂度并不高,因为题目保证了这里的$v_i \leq 10$。

 

proverbs:

最小生成树的两个性质:

1、边权相等的边的个数一定。

2、做完边权为w的所有边时,图的连通性相同。

 1 /**************************************************************
 2     Problem: 1016
 3     User: Tunix
 4     Language: C++
 5     Result: Accepted
 6     Time:8 ms
 7     Memory:1300 kb
 8 ****************************************************************/
 9  
10 //BZOJ 1016
11 #include<vector>
12 #include<cstdio>
13 #include<cstring>
14 #include<cstdlib>
15 #include<iostream>
16 #include<algorithm>
17 #define rep(i,n) for(int i=0;i<n;++i)
18 #define F(i,j,n) for(int i=j;i<=n;++i)
19 #define D(i,j,n) for(int i=j;i>=n;--i)
20 #define pb push_back
21 using namespace std;
22 inline int getint(){
23     int v=0,sign=1; char ch=getchar();
24     while(ch<'0'||ch>'9'){ if (ch=='-') sign=-1; ch=getchar();}
25     while(ch>='0'&&ch<='9'){ v=v*10+ch-'0'; ch=getchar();}
26     return v*sign;
27 }
28 const int N=110,INF=~0u>>2,MOD=31011;
29 typedef long long LL;
30 /******************tamplate*********************/
31 int n,m,fa[N];
32 struct edge{int u,v,w;}e[1010];
33 struct data{int l,r,v;}a[1010];
34 bool cmp(edge a,edge b){return a.w<b.w;}
35 int Find(int x){return fa[x]==x?x:Find(fa[x]);}
36 int sum,ans=1,tot;
37 void dfs(int x,int now,int k){
38     if (now==a[x].r+1){
39         if (k==a[x].v) sum++;
40         return;
41     }
42     int p=Find(e[now].u),q=Find(e[now].v);
43     if (p!=q){
44         fa[p]=q;
45         dfs(x,now+1,k+1);
46         fa[p]=p; fa[q]=q;
47     }
48     dfs(x,now+1,k);
49 }
50 int main(){
51 #ifndef ONLINE_JUDGE
52     freopen("1016.in","r",stdin);
53     freopen("1016.out","w",stdout);
54 #endif
55     n=getint(); m=getint();
56     F(i,1,m){
57         e[i].u=getint(); e[i].v=getint(); e[i].w=getint();
58     }
59     sort(e+1,e+m+1,cmp);
60     int cnt=0,now=0;
61     F(i,1,n) fa[i]=i;
62     F(i,1,m){
63         if (i==1||e[i].w!=e[i-1].w) {a[++cnt].l=i;a[cnt-1].r=i-1;}
64         int p=Find(e[i].u),q=Find(e[i].v);
65         if (p!=q){ fa[p]=q; a[cnt].v++; tot++;}
66     }
67     a[cnt].r=m;
68     if (tot!=n-1){puts("0");return 0;}
69     F(i,1,n) fa[i]=i;
70     F(i,1,cnt){
71         sum=0;
72         dfs(i,a[i].l,0);
73         ans=ans*sum%MOD;
74         F(j,a[i].l,a[i].r){
75             int p=Find(e[j].u),q=Find(e[j].v);
76             if (p!=q) fa[p]=q;
77         }
78     }
79     printf("%d\n",ans);
80     return 0;
81 }
View Code

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

Time Limit: 1 Sec  Memory Limit: 162 MB
Submit: 3312  Solved: 1311
[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,000。数据保证不会出现自回边和重边。注意:具有相同权值的边不会超过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

[Submit][Status][Discuss]
posted @ 2015-04-10 23:37  Tunix  阅读(...)  评论(... 编辑 收藏