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)。

posted @ 2025-04-14 21:44  miao-jc  阅读(12)  评论(0)    收藏  举报