CF1916 Goodbye2023 H1H2 题解
前言
这场比赛打得异常痛苦。
虽然场上H1H2会做,但是考场上太急了导致脑抽,然后就爆掉了。。。
题意
给定 \(n,p,k\),要求:
求有多少个 \(n\times n\) 的在 \(\mod p\) 意义下定义的矩阵,使得其秩为 \(r\)。
\(r\) 从 \(0\) 到 \(k\) 分别输出答案。
题解
容易想到 dp:\(f_{i,r}\) 表示考虑前 \(i\) 行的矩阵,秩为 \(r\) 的数量。
显然有
具体而言,分两种情况:
- 当前秩不增加,那么这行一定是前面的行向量的组合,因为前面的秩 \(r\),所以有 \(2^r\) 种方法。
- 当前秩增加,那么这行一定不是前面的行向量的组合,因为前面的秩为 \(r - 1\),所以有 \(p^n - p^{r - 1}\) 中选择方法。
那么这样就得到如上转移,最终答案是 \(f_{n,0}, \cdots, f_{n,k}\)。
考虑如何快速求解 \(f\) 数组。考虑将 \(f\) 数组画在一个二维平面上,等价于:
每次可以从 \((i,r)\) 走到 \((i+1,r)\)(称为平走),或者从 \((i,r)\) 走到 \((i + 1, r + 1)\),每次行走带着一定的贡献。
可以发现第 2 种转移一定只有 \(r\) 个,且对答案的贡献是确定的,即:
其中,走到 \(f_{i,r}\) 需要进行 \(i - r\) 次第 1 种转移,那么 \(g_{i,r}\) 的意义就明确了。
所以 \(g_{i,r}\) 就是在 \(1\) ~ \(i\) 层种进行若干次(可以为0)平走,使得总平走的次数恰好为 \(r\) 的总贡献,在第 \(j\) 层平走的贡献是 \(p^j\)。
由于有恰好走 \(r\) 步,所以考虑生成函数:\(G_i(x) = \sum_{r\ge0} f_{i,r}x^r\),
从而可以写出 \(G_i(x) = \sum_{j=0}^i \sum_{l\geq0}(p^jx)^l=\sum_{j=0}^i\tfrac{1}{1-p^jx}\),考虑:
所以有
对比两边 \(r\) 次方系数的:
所以就可以得到递推公式:
所以就得到了通项公式:
从而得到了快速求解 \(f\) 的方法:
考虑将 \(r\) 从小到大计算,则后者可以直接算,前者考虑分子分母互相抵消后再计算。
然后这样就可以直接过 H1H2 了。
#include <bits/stdc++.h>
using namespace std;
#define int long long
int rd() {
int x = 0, f = 1;
char ch = getchar();
while (!('0' <= ch && ch <= '9')) {
if (ch == '-') f = -1; ch = getchar();
}
while ('0' <= ch && ch <= '9') {
x = (x << 1) + (x << 3) + (ch ^ 48); ch = getchar();
}
return x * f;
}
void wr(int x) {
if (x < 0) putchar('-'), x = -x;
if (x >= 10) wr(x / 10); putchar(x % 10 + '0');
}
const int N = 5e5 + 10;
const int mod = 998244353;
int ans[N];
int add (int x) {
if (x >= mod) x -= mod; else if (x < 0) x += mod;
return x;
}
int ksm (int x, int y = mod - 2) {
int res = 1; x = add(x);
while (y) {
// cout << y << " " << res << endl;
if (y & 1) res = 1ll * res * x % mod;
x = 1ll * x * x % mod; y >>= 1;
} return res;
}
signed main() {
int n, p, k; n = rd(); p = rd(); k = rd();
int res1 = 1, res2 = 1;
for (int r = 0; r <= min(n,k); ++r) {
ans[r] = 1ll * res1 * res2 % mod;
res1 = 1ll * res1 * add(ksm(p, n) - ksm(p, r)) % mod;
res2 = 1ll * res2 * ksm(ksm(p, r + 1) - 1) % mod * add(ksm(p, n - r) - 1) % mod;
}
for (int r = 0; r <= k; ++r) printf ("%d ", ans[r]);
return 0;
}
话说,上面最后的式子是可以用找规律的?不清楚了。