JZOJ 6979. 【2021.02.03冬令营模拟】天各一方
题目大意
- 求 n n n个点组成的所有不同连通图中, 1 1 1到 n n n的最短距离之和。
- n ≤ 400 nle400 n≤400
题解
-
很关键的一点是,因为是所有连边的方案,所以 1 1 1到 n n n和 1 1 1到 2 2 2、 1 1 1到 3 3 3…… 1 1 1到 n − 1 n-1 n−1本质上都是相同的,所以答案可以转化为 1 1 1到剩下每个点的最短距离之和再除以 n − 1 n-1 n−1。
-
试着把所有的点分层,距离即为它们层数的差值。初始时均在第 0 0 0层,通过DP使除了 1 1 1以外所有点下移。
-
设 f i , j f_{i,j} fi,j为当前确定好了 i i i个点,其中有 j j j个点确定了放在最后一层,而剩下的 n − i n-i n−i此时也被移到了最后一层,但还将继续下移的方案数, g i , j g_{i,j} gi,j表示对应的总和。注意这里并不需要记录层数,因为它对转移无影响。
-
转移时枚举下一层留下多少个 k k k,为了保证层数能对应距离,所以下一层只能和当前层连边,不能和上一层连边,同时层内可以自由连边,于是有:
-
f i , i + k = ∑ f i , j ∗ ( n − i k ) ∗ ( 2 j − 1 ) k ∗ 2 k ( k − 1 ) 2 f_{i,i+k}=sum f_{i,j}*{n-ichoose k}*(2^j-1)^k*2^{frac{k(k-1)}{2}} fi,i+k=∑fi,j∗(kn−i)∗(2j−1)k∗22k(k−1)
-
g i , i + k = ∑ ( g i , j + ( n − i ) ∗ f i , j ) ∗ ( n − i k ) ∗ ( 2 j − 1 ) k ∗ 2 k ( k − 1 ) 2 g_{i,i+k}=sum ig(g_{i,j}+(n-i)*f_{i,j}ig)*{n-ichoose k}*(2^j-1)^k*2^{frac{k(k-1)}{2}} gi,i+k=∑(gi,j+(n−i)∗fi,j)∗(kn−i)∗(2j−1)k∗22k(k−1)
代码
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define N 410
#define ll long long
int c[N][N], e[N * N], s[N][N], P;
ll f[N][N], g[N][N];
ll ksm(ll x, ll y) {
if(!y) return 1;
ll l = ksm(x, y / 2);
if(y % 2) return l * l % P * x % P;
return l * l % P;
}
int main() {
int n, i, j, k;
scanf("%d%d", &n, &P);
e[0] = 1;
for(i = 1; i <= n * n; i++) e[i] = e[i - 1] * 2 % P;
for(i = 0; i <= n; i++) {
c[i][i] = c[i][0] = s[i][0] = 1;
for(j = 1; j < i; j++) c[i][j] = (c[i - 1][j - 1] + c[i - 1][j]) % P;
for(j = 1; j <= n; j++) s[i][j] = (ll)s[i][j - 1] * (e[i] - 1) % P;
}
f[1][1] = 1, g[1][1] = 0;
for(i = 1; i < n; i++) {
for(j = 1; j <= i; j++) {
for(k = 1; i + k <= n; k++) {
ll t = (ll)c[n - i][k] * s[j][k] % P * e[k * (k - 1) / 2] % P;
(f[i + k][k] += f[i][j] * t) %= P;
(g[i + k][k] += g[i][j] * t + f[i][j] * t % P * (n - i)) %= P;
}
}
}
ll ans = 0;
for(i = 1; i <= n; i++) (ans += g[n][i]) %= P;
printf("%lld
", ans * ksm(n - 1, P - 2) % P);
return 0;
}
自我小结
- 对若干个等价的方案之一计数,可以先计算总和再除以相应的个数。
- 和图或树有关的计数题可以从小数据入手,尽可能多地设出图的各种性质作为状态,尝试转移,最后再考虑优化。