2025杭电多校——6
键帽
https://acm.hdu.edu.cn/contest/problem?cid=1155&pid=1009

这是借助的别人的思路:
https://www.cnblogs.com/TianTianChaoFangDe/p/18822660
用dp[i][j]表示长度为 i 的字符串,后 j 个字符全是元音。
可以得到:

题解讲的比我好。
还是具体看代码吧。
点击查看代码
#pragma GCC optimize(1)
#pragma GCC optimize(2)
#pragma GCC optimize(3,"Ofast","inline")
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define int long long
typedef pair<int, int> pii;
const int N = 1e6 + 100;
const int inf = LLONG_MAX;
const double eps = 1e-6;
const int mod = 1e9+7;
long long ext_gcd(long long a, long long b, long long &x, long long &y) {
if (b == 0) {
x = 1;
y = 0;
return a;
}
long long d = ext_gcd(b, a % b, y, x);
y -= a / b * x;
return d;
}
// 计算 a 的模逆元
long long inv(long long a, long long mod) {
long long x, y;
ext_gcd(a, mod, x, y);
return (x % mod + mod) % mod;
}
class Z {
public:
long long val;
Z() : val(0) {}
Z(long long v) : val(v % mod) {}
Z(const Z &other) : val(other.val) {}
Z& operator=(const Z &other) {
val = other.val;
return *this;
}
Z operator+(const Z &other) const {
return Z(val + other.val);
}
Z operator-(const Z &other) const {
return Z(val - other.val + mod);
}
Z operator*(const Z &other) const {
return Z(val * other.val);
}
Z operator/(const Z &other) const {
return Z(val * inv(other.val, mod));
}
Z& operator+=(const Z &other) {
val = (val + other.val) % mod;
return *this;
}
Z& operator-=(const Z &other) {
val = (val - other.val + mod) % mod;
return *this;
}
Z& operator*=(const Z &other) {
val = (val * other.val) % mod;
return *this;
}
Z& operator/=(const Z &other) {
val = (val * inv(other.val, mod)) % mod;
return *this;
}
friend std::ostream& operator<<(std::ostream &os, const Z &z) {
os << z.val;
return os;
}
};
vector<Z> pow5;
void miaojiachun() {
int n, k;
cin >> n >> k;
vector<array<Z, 2>> dp(n + 1, array<Z, 2>{0});
vector<Z> pre(n + 1);
dp[0][0] = 1;
dp[0][1] = 0;
pre[0] = pow5[n];
Z ans = 0;
for (int i = 1; i <= n; i++) {
int l = max(0ll, i - k);
int r = i - 1;
Z now;
if (l == 0) {
now = pre[r];
}else {
now = pre[r] - pre[l - 1];
}
dp[i][1] = now / pow5[n - i];
dp[i][0] = (dp[i - 1][0] + dp[i - 1][1]) * 21;
pre[i] = pre[i - 1] + dp[i][0] * pow5[n - i];
}
for (int i = 0; i <= k; i++) {
ans += dp[n - i][0] * pow5[i];
}
cout << ans << endl;
}
signed main() {
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
int ING = 1;
pow5.resize(N);
pow5[0] = 1;
for (int i = 1; i < N; i++) {
pow5[i] = pow5[i - 1] * 5;
}
cin >> ING;
while (ING--) {
miaojiachun();
}
return 0;
}
上面是偷的取模类的板子。
利用自定义取模类定义数据类型。我们可以利用题解给出的公式,先对5的次幂进行预处理。
dp[i][0]表示以辅音字母结尾,dp[i][1]表示以元音字母结尾。

那么dp[i][1]就等于从该字母开始,前面(0~k)个字母为元音字母的情况相加。

dp[i][0] 就等于前面所有的合法情况 * 辅音字母个数。


这个是需要学习的地方。
前缀和数组 pre[] 的定义与数学推导
1. 前缀和数组定义
pre[i] 的数学定义为:

其中:
dp[j][0]:长度为j的字符串中以辅音结尾的方案数5^{n-j}:剩余n-j个位置全填元音的总方案数(每个元音有5种选择)
2. 定义动机
该定义是为了优化 dp[i][1](以元音结尾的方案数)的转移方程:

3. 关键数学变换
利用指数运算性质:

将转移方程改写为:

4. 前缀和优化
观察到求和部分恰好是前缀和数组的区间和:

优化效果:
- 原复杂度:每次计算需要 O(k) 时间
- 优化后:通过前缀和实现 O(1) 查询
5. 算法优势
| 方法 | 时间复杂度 | 空间复杂度 |
|---|---|---|
| 直接计算 | O(nk) | O(n) |
| 前缀和优化 | O(n) | O(n) |
该优化将算法效率从二次复杂度降低到线性复杂度,适用于大规模数据(如 n ≤ 10^6)。

浙公网安备 33010602011771号