题解:P12495 [集训队互测 2024] 链覆盖
考虑 原问题 怎么做:实际上是一个贪心,每次贪心选择最优的叶子节点,正确性可以使用 Exchange Argument 证明。上述贪心可以看作先对
长剖视角看起来更为容易来刻画这个问题。不妨就从长剖视角入手。
回想长剖,长剖的过程需要维护一个
考虑这个过程有什么用,我们设
-
- 最大的
up 使得s_{up}>0 的数满足s_{up}=1 ; -
最后一条则是因为对于每个
上面三条的必要性是显然的,充分性可以考虑使用构造证明。我们将
我们发现
考虑从
我们先考虑怎么求这个系数,设
确认父亲有几种选择,要么是选择一个
三个东西都是可以分开递推求的,那么我们猜测合在一起也可以递推求:
那么我们可以在
复杂度是
但是用上述 dp 算答案似乎有一点困难,这是因为
但是这样的话我们需要再倒着做一遍 dp 求出前缀的情况。设
最后枚举分界点
然后此时就可以做了,因为最后枚举分界点
下面代码是
// https://www.luogu.com.cn/article/eadbop1r
#include <bits/stdc++.h>
using namespace std;
constexpr int N = 310;
int P;
inline int add(int x, int y) { if (x + y >= P) return x + y - P; return x + y; }
inline int sub(int x, int y) { if (x - y < 0) return x - y + P; return x - y; }
inline int mul(int x, int y) { return 1LL * x * y % P; }
inline void Add(int& x, int y) { x = add(x, y); }
inline void Sub(int& x, int y) { x = sub(x, y); }
inline void Mul(int& x, int y) { x = mul(x, y); }
inline int qpow(int x, int y = P - 2) {
int z = 1;
while (y) {
if (y & 1) z = mul(z, x);
x = mul(x, x);
y >>= 1;
}
return z;
}
int n;
int C[N][N];
// coef[a][b][c] 表示有 a 个 d[u] = i(s[i]=a), b 个 d[u] = i + 1, c 个 d[u] > i + 1
int coef[N][N][N];
// f 从大到小 dp,s[i] = j, sum s[i,n] = k,s[i,n]的权值和
int f[N][N][N];
// g 从小到大 dp,s[i] = j, sum s[i,n] = k,s[1,i)的权值和
int g[N][N][N];
int tag[N][N][N];
int ans[N][N];
int main() {
freopen("counting.in", "r", stdin);
freopen("counting.out", "w", stdout);
cin >> n >> P;
if (n == 1) {
cout << "1" << "\n";
return 0;
}
C[0][0] = 1;
for (int i = 1; i <= n; i++) {
C[i][0] = 1;
for (int j = 1; j <= n; j++) {
C[i][j] = add(C[i - 1][j], C[i - 1][j - 1]);
}
}
for (int a = 0; a <= n; a++) {
for (int c = 0; c <= n; c++) {
coef[a][0][c] = qpow(c, a);
}
for (int b = 1; b <= a; b++) {
for (int c = 0; c <= n; c++) {
coef[a][b][c] = sub(coef[a][b - 1][c + 1], coef[a][b - 1][c]);
}
}
}
for (int i = n; i >= 1; i--) {
// 处理 i=up,作为根的那个
Add(f[i][1][1], 1);
int n1 = n / (i + 1), n2 = n / i;
for (int j = 0; j <= n1; j++) {
for (int k = j; k <= n; k++) if (f[i + 1][j][k]) {
// 枚举 s[i] 是什么
for (int l = j; l <= n2 && l + k <= n; l++) {
Add(f[i][l][l + k], mul(f[i + 1][j][k], mul(coef[l][j][k - j], C[n - k][l])));
}
}
}
}
for (int i = 1; i <= n; i++) {
Add(g[1][i][n], 1);
}
for (int i = 2; i <= n; i++) {
int n1 = n / (i - 1), n2 = n / i;
for (int j = 0; j <= n1; j++) {
for (int k = j; k <= n; k++) if (g[i - 1][j][k]) {
// 枚举 s[i] 是什么
for (int l = 0; l <= n2 && l <= j && l <= k - j; l++) {
Add(g[i][l][k - j], mul(g[i - 1][j][k], mul(coef[j][l][k - j - l], C[n - (k - j)][j])));
}
}
}
}
// 枚举 s[i] <= k, s[i - 1] > k
for (int i = 2; i <= n; i++) {
// s[i] = a, s[i - 1] = b
int n1 = n / i, n2 = n / (i - 1);
for (int a = 1; a <= n1; a++) {
for (int b = a + 1; b <= n2; b++) {
// s[i, n] = j
for (int j = a; j + b <= n; j++) {
int val = mul(mul(f[i][a][j], g[i - 1][b][j + b]), mul(coef[b][a][j - a], C[n - j][b]));
/*
// 对 ans[k][(i - 1) * k + j] 产生贡献
Add(tag[a][i - 1][j], val);
Sub(tag[b][i - 1][j], val);
*/
// k in [a, b - 1]
for (int k = a; k < b; k++) if ((i - 1) * k + j <= n) {
Add(ans[k][(i - 1) * k + j], val);
}
}
}
}
}
/*for (int i = 1; i <= n; i++) {
for (int j = 0; j <= n; j++) {
for (int k = 0; k <= n; k++) {
Add(tag[i + 1][j][k], tag[i][j][k]);
if (j * i + k <= n) Add(ans[i][j * i + k], tag[i][j][k]);
}
}
}*/
// 处理答案最后一列,用 n^{n-2} 减去
for (int i = 1; i <= n; i++) {
int sum = 0;
for (int j = 1; j < n; j++) Add(sum, ans[i][j]);
ans[i][n] = sub(qpow(n, n - 2), sum);
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
cout << ans[i][j] << " ";
}
cout << "\n";
}
}