BZOJ 1016 JSOI 2008 巨额奖金 最小生成树计数

最小生成树有多少个。

注意到最小生成树中选了相同权值的边多少条是不变的。有kruskal可知。
因此考虑在求出最小生成树后枚举每种权值的边。
然后所有情况乘起来即可。

#include <cstdio>
#include <cstring>
#include <vector>
#include <algorithm>
#define FOR(i,j,k) for(i=j;i<=k;++i)
#define rep(i,j,k) for(i=j;i<k;++i)
using namespace std;
typedef long long ll;
const int mod = 31011, N = 2005;
ll read() {
    ll s = 0, f = 1; char ch = getchar();
    for (; ch < '0' || ch > '9'; ch = getchar()) if (ch == '-') f = -1;
    for (; '0' <= ch && ch <= '9'; ch = getchar()) s = s * 10 + ch - '0';
    return s * f;
}
struct Edge { int a, b, i; ll c; } e[N];
bool operator< (Edge a, Edge b) { return a.c < b.c;}
int fa[N], vis[N], color[N], cnt[N], ts, n, m, cc = 0;
ll ans = 0;
vector<int> belong[N];
int find(int x) {
    return x == fa[x] ? x : fa[x] = find(fa[x]);
}
bool kruskal() {
    int i, j, a, b, tot = 0;
    FOR(i,1,n) fa[i] = i;
    sort(e + 1, e + m + 1);
    FOR(i,1,m) {
        a = find(e[i].a), b = find(e[i].b);
        if (a != b) {
            fa[a] = b; ans += e[i].c;
            FOR(j,1,cc) if (color[j] == e[i].c)
            { ++cnt[j]; break; }
            if (++tot >= n - 1) return 1;
        }
    }
    return 0;
}
bool ok() {
    int i, j, a, b; ll z = 0;
    FOR(i,1,n) fa[i] = i;
    FOR(i,1,m) if (vis[e[i].i] != ts) {
        a = find(e[i].a), b = find(e[i].b);
        if (a != b) fa[a] = b, z += e[i].c;
    }
    return z == ans;
}
int calc(int j) {
    int z = 0;
    for (; j; j -= j & -j) ++z;
    return z;
}
int main() {
    int i, j, k, z = 1;
    n = read(); m = read();
    FOR(i,1,m) e[i].a = read(), e[i].b = read(), e[i].c = read(), e[i].i = i;
    FOR(i,1,m) if (!vis[i]) {
        color[++cc] = e[i].c;
        belong[cc].push_back(i);
        FOR(j,i+1,m) if (e[i].c == e[j].c)
            vis[j] = 1, belong[cc].push_back(j);
    }
    if (!kruskal()) return puts("0"), 0;
    memset(vis, 0, sizeof vis); ts = 0;
    FOR(j,1,cc) {
        if (!cnt[j]) continue;
        int tmp = 0;
        rep(i,0,(1<<belong[j].size()))
            if (calc(i) == cnt[j]) {
                ++ts;
                rep(k,0,belong[j].size())
                    if (!((i >> k) & 1))
                        vis[belong[j][k]] = ts;
                tmp += ok();
            }
        if (tmp) z = (1ll * z * tmp) % mod;
    }
    printf("%d", z);
    return 0;
}

【问题描述】
G 城市发展迅速,考虑人口的迅速发展,G 城市的交通压力不断增强。现在,G 城市正
在规划建设一个交通枢纽,以减轻交通的压力。
G 城市包括 n 个区,有些区之间有双向的干道存在。新型交通枢纽建设在这些干道的基
础上,将其中的部分干道改进为新型干道。改进后,干道能承受的压力可以比原来增加几十
倍。同时,要求任何两个区之间都可以只通过新型干道直接或间接连接。政府已经预测出每
条干道改进为新型干道的费用,并希望建设新型干道的总费用最小,并以巨额奖金向市民征
集方案。政府很快发现费用最小的方案不是唯一,所以决定将奖金平分给每一种方案的第一
设计者。如果一个人设计的费用是最小的而且前面没人设计出和他一模一样的方案,则他可
获奖。
设计的方案数会很多。如果最后获奖者太多,巨额的资金分到每个人头上的也不会太
多。因此,让你先算下可行的方案数是多少。
【文件输入】输入文件:award.in。第一行两个数 n(1≤n≤100),m(1≤m≤1000),分别
表示该市有多少个区和有多少条干道。接下来 m 行,每行三个数 ai,bi,ci(1≤ai、bi≤n,
1≤ci≤1000000000),表示 ai 区和 bi 区之间有一条干道,如果改进需要费用 ci 的费用。
注意:具有相同权值的边不会超过10条。
考试的时候没有这条简直坑死人。。。
【文件输出】输出文件:award.out。最小费用的方案总数。输出方案总数除以 31011 的摸
即可。
【输入输出样例】
Input
4 6
1 2 1
1 3 1
1 4 1
2 3 2
2 4 1
3 4 1
Output
8

你可能感兴趣的:(计数,最小生成树,bzoj,JSOI,省选)