$Link$
给定一个 $n$ 个点 $m$ 条边的简单无向连通图,其中图的前 $n-1$ 条边构成了图的一棵生成树。
你需要为每条边分配一个范围在 $[1,m]$ 的权值,且每种权值只能用一次。
定义一种分配方案的得分是这个图的最小生成树的权值和。
求所有可能的分配方案中,前 $n-1$ 条边是图的最小生成树的方案的得分和,对 $10^9+7$ 取模。
$2\le n\le20,n-1\le m\le\frac{n(n-1)}{2}$。
$n$ 很小,不由得联想到与 $2^n$ 有关的做法。
考虑类似 $Kruskal$ 的做法,按照 $1\sim m$ 的顺序,给每条边赋上权值。
那权值 $i$ 可以赋给某条边,当且仅当这条边连接的两个点已经处于同一个联通块中。
结合以上的所有想法,考虑设 $f_{i,S}$ 表示正在分配权值 $i$,编号为 $1\sim n-1$ 的边中,状态 $S$ 已经被分配最终的分配方案数,$g_{i,S}$ 表示答案。
那么转移无非就是把 $i$ 分配给编号为 $1\sim n-1$ 中的边,或者是其他的边两种。
也即 $f_{i,S}\to f_{i+1,S}/f_{i+1,S|j}$。
第二种直接转移就好,第一种需要考虑可以选择哪些边,这里显然可以使用并查集预处理出 $cnt_S$,表示如果已经分配了状态 $S$,有哪些边可以被选。
$g$ 的转移同理,在第二种转移时多乘一个权值即可。
剩下要做的就只有把 $f$ 和 $g$ 滚动一下了,时间复杂度 $O(mn2^n)$,加点剪枝卡卡常就过了。
$code$
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112
| #include <iostream> #include <cstdlib> #include <cstdio> #define ll long long using namespace std; inline int read() { int f = 1, x = 0; char ch;
do{ ch = getchar(); if (ch == '-') f = -1; }while(ch < '0' || ch > '9'); do{ x = x * 10 + ch - '0'; ch = getchar(); }while(ch >= '0' && ch <= '9'); return f * x; } const int N = 20; const int mod = 1e9 + 7;
int n, m; struct Edge { int from, to; } edge[N * N + 1]; ll f[1 << N], g[1 << N]; int cnt[1 << N]; namespace dsu { int fa[N + 1], sz[N + 1]; inline void init() { for (int i = 1; i <= n; i++) { fa[i] = i; sz[i] = 1; } return; } inline int gfa(int x) { if (fa[x] != x) fa[x] = gfa(fa[x]); return fa[x]; } inline void update() { for (int i = 1; i <= n; i++) fa[i] = gfa(i); return; } inline void merge(int x, int y) { x = gfa(x); y = gfa(y); if (x != y) { if (sz[x] < sz[y]) swap(x, y); fa[y] = x; sz[x] += sz[y]; } return; } }
inline void dp() { for (int S = 0; S < 1 << (n - 1); S++) { dsu::init(); for (int i = 1; i <= n - 1; i++) if (S & (1 << (i - 1))) { dsu::merge(edge[i].from, edge[i].to); cnt[S]++; } dsu::update(); for (int i = n; i <= m; i++) if (dsu::fa[edge[i].from] == dsu::fa[edge[i].to]) cnt[S]++; } f[0] = 1; for (int i = 1; i <= m; i++) { for (int S = (1 << (n - 1)) - 1; S >= 0; S--) { f[S] %= mod; g[S] %= mod; if (!f[S]) continue; for (int j = 1; j <= n - 1; j++) { if (S & (1 << (j - 1))) continue; f[S | (1 << (j - 1))] += f[S]; g[S | (1 << (j - 1))] += g[S] + i * f[S]; } f[S] = f[S] * (cnt[S] - i + 1); g[S] = g[S] * (cnt[S] - i + 1); } } return; } int main() { n = read(); m = read(); for (int i = 1; i <= m; i++) edge[i] = { read(), read() };
dp();
printf("%d\n", g[(1 << (n - 1)) - 1] % mod); return 0; }
|