zoukankan      html  css  js  c++  java
  • bzoj1016 [JSOI2008]最小生成树计数

    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

    正解:$kruskal$+搜索/矩阵树定理。

    这道题涉及到一些定理:

    定理一:如果 $A, B$ 同为 $G$ 的最小生成树,且 $A$ 的边权从小到大为 $w(a_1), w(a_2), w(a_3), cdots w(a_n)$,$B$ 的边权从小到大为 $w(b_1),w(b_2), w(b_3), cdots w(b_n)$,则有 $w(a_i) = w(b_i)$。
    证明:设$A, B$ 第一个不同的边的下标为 $i$,不妨设 $w(a_i) le w(b_i)$,如果不存在这样的 $i$,无需证明。
    情况一:$a_i$ 在 $B$ 中,为 $b_j$。那么显然有 $j>i$(否则 $i$ 不是第一个不同的边),则有 $w(a_i)le w(b_i) le w(b_j) = w(a_i)$,所以有 $w(a_i) = w(b_i) = w(b_j)$,所以可以调换 $b_i, b_j$ 的位置,$B$ 的权值排列不会改变, $A$ 与 $B$ 这样前 $i$ 条边均为相等,可以递归下去证明。
    情况二:$a_i$ 不在 $B$ 中。考虑将 $a_i$ 加入 $B$,则形成了一个环,环中的权值 $vle w(a_i)$(否则 $B$ 不是最小生成树),且一定有一条边 $b_j$ 不在 $B$ 中,此时仍有 $j>i$(否则 $i$ 不是第一个不同的边),所以有 $w(a_i)le w(b_i) le w(b_j) le w(a_i)$,所以仍有 $w(a_i) = w(b_i) = w(b_j)$,可以将 $b_j$ 替换为 $a_i$,$B$ 的权值排列不会改变且仍为最小生成树,仍然调换 $b_i, b_j$ 的位置,$B$ 的权值排列仍会改变,这样 $A$ 与 $B$ 前 $i$ 条边均为相等,可以递归下去证明。这样换边不会有问题,因为 Kruskal 算法中唯一的状态就是连通性,我们没有改变其连通性,所以是可以递归证明的。

    定理二:如果 $A, B$ 同为 $G$ 的最小生成树,如果 $A, B$ 都从零开始从小到大加边($A$ 加 $A$ 的边,$B$ 加 $B$ 的边)的话,每种权值加完后图的联通性相同。
    证明:归纳法证明,没有边时显然成立,假设对于权值小于 $v$ 的成立。考虑权值为 $v$ 的边,如果连通性不相同,必然存在 $u, v$ 两点间连通性不同,假设 $A$ 中 $u, v$ 联通,根据 $A, B$ 所有小于 $v$ 的边连通性相同,所以必然存在一条 $u ightarrow v$ 权值为 $v$ 的边,而根据 Kruskal 算法的执行过程, $B$ 不可能不加这条边,所以两棵最小生成树 $A, B$ 各自权值小于等于 $v$ 的边仍然满足连通性相同。由归纳法可知定理二成立。

    定理三:如果在最小生成树 $A$ 中权值为 $v$ 的边有 $k$ 条,用任意 $k$ 条权值为 $v$ 的边替换 $A$ 中的权为 $v$ 的边且不产生环的方案都是一棵合法最小生成树。
    证明:根据之前的定理,其余的边造成的连通性是定的,权值和也是定的,那么选 $k$ 条不产生环一定能形成一棵树,而且权值与最小生成树的权值一样,故也是最小生成树。

    ——sengxian

    于是我们可以先跑一遍最小生成树。然后对于每种权值的边,用搜索或矩阵树定理统计方案数(因为相同边权的边不超过10条所以爆搜能过)。然后乘法原理,直接把每种边的答案相乘就行了。

    然而这题如果是搜索并查集不能写路径压缩。。坑了我好久。。

     1 //It is made by wfj_2048~
     2 #include <algorithm>
     3 #include <iostream>
     4 #include <complex>
     5 #include <cstring>
     6 #include <cstdlib>
     7 #include <cstdio>
     8 #include <vector>
     9 #include <cmath>
    10 #include <queue>
    11 #include <stack>
    12 #include <map>
    13 #include <set>
    14 #define rhl (31011)
    15 #define inf (1<<30)
    16 #define N (1010)
    17 #define il inline
    18 #define RG register
    19 #define ll long long
    20 #define File(s) freopen(s".in","r",stdin),freopen(s".out","w",stdout)
    21 
    22 using namespace std;
    23 
    24 struct edge{ int u,v,w; }g[100010];
    25 struct node{ int l,r; }a[100010];
    26 
    27 int fa[100010],sum[100010],n,m,k,cnt,res,ans;
    28 
    29 il int gi(){
    30     RG int x=0,q=1; RG char ch=getchar(); while ((ch<'0' || ch>'9') && ch!='-') ch=getchar();
    31     if (ch=='-') q=-1,ch=getchar(); while (ch>='0' && ch<='9') x=x*10+ch-48,ch=getchar(); return q*x;
    32 }
    33 
    34 il int cmp(const edge &a,const edge &b){ return a.w<b.w; }
    35 
    36 il int find(RG int x){ return fa[x]==x ? fa[x] : find(fa[x]); }
    37 
    38 il void dfs(RG int cnt,RG int x,RG int v){
    39     if (x>a[cnt].r){ if (v==sum[cnt]) res++; return; }
    40     dfs(cnt,x+1,v); RG int p=find(g[x].u),q=find(g[x].v);
    41     if (p!=q) fa[p]=q,dfs(cnt,x+1,v+1),fa[p]=p,fa[q]=q;
    42     return;
    43 }
    44 
    45 il void work(){
    46     n=gi(),m=gi();
    47     for (RG int i=1;i<=m;++i) g[i].u=gi(),g[i].v=gi(),g[i].w=gi();
    48     sort(g+1,g+m+1,cmp); for (RG int i=1;i<=n;++i) fa[i]=i; cnt=0;
    49     for (RG int i=1;i<=m;++i){
    50     if (g[i].w!=g[i-1].w) cnt++;
    51     if (!a[cnt].l) a[cnt].l=i; a[cnt].r=i;
    52     RG int p=find(g[i].u),q=find(g[i].v);
    53     if (p!=q) fa[p]=q,k++,sum[cnt]++;
    54     }
    55     if (k!=n-1){ printf("0"); return; }
    56     for (RG int i=1;i<=n;++i) fa[i]=i; ans=1;
    57     for (RG int i=1;i<=cnt;++i){
    58     res=0,dfs(i,a[i].l,0),(ans*=res)%=rhl;
    59     for (RG int j=a[i].l;j<=a[i].r;++j){
    60         int p=find(g[j].u),q=find(g[j].v);
    61         if (p!=q) fa[p]=q;
    62     }
    63     }
    64     printf("%d",ans); return;
    65 }
    66 
    67 int main(){
    68     File("counttree");
    69     work();
    70     return 0;
    71 }
  • 相关阅读:
    利用border制作三角形原理
    JavaScript中null和undefined
    localStorage 如何存储JSON数据并读取JSON数据
    常见的几种浏览器内核简单介绍
    关于Banner设计的促销氛围
    分数分配
    餐饮管理系统
    餐馆管理之点菜
    餐饮管理之结账
    餐饮管理之菜品管理
  • 原文地址:https://www.cnblogs.com/wfj2048/p/6575562.html
Copyright © 2011-2022 走看看