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

解题思路:第一眼,这什么题?难道是Matrix-Tree?但是这个权值怎么处理。。实际上Matrix-Tree也是可以做的,方法就是对于每个联通块建立Matrix-Tree,然后根据乘法原理把这些联通块的答案乘起来就得到最后的方案数了。我没有写这种做法,这种比较麻烦,看了hzwer神牛的博客,会了一种更简单的做法。首先要注意到这里权值种数不超过10,排序以后先做一遍最小生成树,得出每种权值的边使用的数量x。然后对于每一种权值的边搜索,得出每一种权值的边选择方案,然后乘法原理, 写法借鉴了hzwer神牛代码。以及有什么trick已经在代码里注释出来了。

代码如下:

#include <bits/stdc++.h>
using namespace std;
const int mod = 31011;
struct edge1{
    int a, b, c;
    edge1(){}
    edge1(int a, int b, int c) : a(a), b(b), c(c) {}
    bool operator <(const edge1 &rhs) const{
        return c < rhs.c;
    }
}E1[10005];
struct edge2{
    int st, en, len;
    edge2(){}
    edge2(int st, int en, int len) : st(st), en(en), len(len) {}
}E2[10005]; //存的是边权想等的一个联通块

int fa[105];
int n, m, tot, ans = 1;
int cnt, sum;
int find_set(int x){ //不能用路径压缩,因为要连通块分离
    if(x == fa[x]) return x;
    else return find_set(fa[x]);
}
void dfs(int x, int now, int k){ //起点,终点,联通块的权值和
    if(now == E2[x].en + 1){
        if(k == E2[x].len){
            sum++;
        }
        return;
    }
    int fx = find_set(E1[now].a), fy = find_set(E1[now].b);
    if(fx != fy){
        fa[fx] = fy;
        dfs(x, now + 1, k + 1);
        fa[fx] = fx;
        fa[fy] = fy;
    }
    dfs(x, now + 1, k);
}
int main(){
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; i++) fa[i] = i; //每个点都在一个连通块
    for(int i = 1; i <= m; i++){ //Kruskal cal MST
        int u, v, w;
        scanf("%d%d%d", &u, &v, &w);
        E1[i] = edge1(u, v, w);
    }
    sort(E1 + 1, E1 + m + 1);
    for(int i = 1; i <= m; i++){
        if(E1[i].c != E1[i-1].c){
            E2[++cnt].st = i;
            E2[cnt-1].en = i - 1;
        }
        int x = find_set(E1[i].a), y = find_set(E1[i].b);
        if(x != y){
            fa[x] = y;
            E2[cnt].len++;
            tot++;
        }
    }
    E2[cnt].en = m;
    if(tot != n - 1){
        puts("0");
        return 0;
    }
    for(int i = 1; i <= n; i++) fa[i] = i; //求出生成树之后,还原连通块
    for(int i = 1; i <= cnt; i++)
    {
        sum = 0;
        dfs(i, E2[i].st, 0);
        ans = (ans * sum) % mod;
        for(int j = E2[i].st; j <= E2[i].en; j++){ //消除前面联通块的影响,不然会WA,sample就是前一阶段有没有用完的点,并且和现在的联通块连边
            int x = find_set(E1[j].a), y = find_set(E1[j].b);
            if(x != y){
                fa[x] = y;
            }
        }
    }
    printf("%d\n", ans);
    return 0;
}