$Link$
有一个 $n$ 个点的无向图,对于每个点给出一个 $p_i$,表示 $i$ 和 $p_i$ 要在同一个联通分量内。
现在有一些点的 $p_i=-1$,表示还不确定。现在你要对于每种可能求出满足条件的最小的边数的和。
对 $10^9+7$ 取模。
$2\le N\le5000,p_i\not=i$。
下文我们设有 $k$ 个点 $p_i=-1$。
最小的边数实际上就是点数 $-$ 环数。
显然如果我们把 $i$ 向 $p_i$ 连边,形成的是一棵由树和基环内向树构成的森林。
对于基环内向树,显然对于 $(n-1)^k$ 种方案它都对答案有一个环的贡献。
对于树,注意到只有根节点向外一条边还未确定,而这条边如果连到了基环树上就不会产生环了。
唯一会产生环的只有 $a$ 树连向 $b$ 树 $\cdots$ 再连向 $a$ 树,这样形成了一个环。
那我们设有 $m$ 棵树,每棵树的大小为 $sz_i$,选出了 $x_1,x_2,\cdots,x_p$ 连成了一个环。
那么方案数就是 $(p-1)!\prod_{i=1}^{p}sz_{x_i}$。
前半部分表示这 $p$ 棵树按什么顺序顺次连接,后半部分则是连接每棵树的哪个点,按照乘法原理乘起来。
剩下的 $m-p$ 棵树随便连,这个环的贡献都存在。所以最后对答案的贡献要再乘上一个 $(n-1)^{m-p}$。
使用简单 $dp$,对于每个 $p$ 计算 $\sum\prod_{i=1}^{p}sz_{x_i}$ 即可。
要注意 $p=1$ 时相当于根向自己这棵树连边形成了一个环,由于不能连向自己所以要减掉一点东西。
时间复杂度 $O(n^2)$。
$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 113 114 115 116 117
| #include <iostream> #include <cstdlib> #include <cstdio> 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 = 5e3; const int mod = 1e9 + 7;
int n, sum; int p[N + 1]; namespace dsu { int fa[N + 1], sz[N + 1], type[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 (x != fa[x]) fa[x] = gfa(fa[x]); return fa[x]; } inline void merge(int x, int y) { x = gfa(x); y = gfa(y); if (x == y) return; if (sz[x] < sz[y]) swap(x, y); fa[y] = x; sz[x] += sz[y]; return; } } int type[N + 1], sz1[N + 1], sz[N + 1], tot; int f[N + 1][N + 1], fac[N + 1]; int ans;
inline int pow(int x, int y) { int ans = 1;
while (y) { if (y & 1) ans = 1ll * ans * x % mod; x = 1ll * x * x % mod; y >>= 1; } return ans; } inline void dp() { f[0][0] = 1; for (int i = 1; i <= sum; i++) { f[i][0] = 1; for (int j = 1; j <= i; j++) f[i][j] = (f[i - 1][j] + 1ll * f[i - 1][j - 1] * sz[i] % mod) % mod; } return; } int main() { n = read(); dsu::init(); fac[0] = 1; for (int i = 1; i <= n; i++) { p[i] = read(); if (p[i] != -1) dsu::merge(i, p[i]); else sum++; fac[i] = 1ll * fac[i - 1] * i % mod; } for (int i = 1; i <= n; i++) { int root = dsu::gfa(i); sz1[root]++; if (p[i] == -1) type[root] = 1; }
for (int i = 1; i <= n; i++) { if (!sz1[i]) continue; if (!type[i]) ans = (ans + pow(n - 1, sum)) % mod; else sz[++tot] = sz1[i]; } dp(); f[sum][1] -= sum; for (int i = 1; i <= sum; i++) ans = (ans + 1ll * f[sum][i] * fac[i - 1] % mod * pow(n - 1, sum - i) % mod) % mod;
printf("%d\n", (1ll * n * pow(n - 1, sum) % mod - ans + mod) % mod); return 0; }
|