Codeforces 360C DP 计算贡献
题意:给你一个长度为n的字符串,定义两个字符串的相关度为两个串对应的子串中第一个串字典序大于第二个串的个数。现在给你相关度,和第二个串,问满足条件的第一个串有多少个?
思路:设dp[i][j]为填了前i个字符,后面的字符和s相同,相关度为j的方案数。现在有两种转移:
1:i位置填的字符大于s[i], 那么我们假设i前面第一个与s不相等的位置是l(即s[l + 1]到s[i]是相等的), 那么相当于左端点在[l + 1, i], 右端点在[i, n]的区间都会产生贡x献,那么从dp[l][j - (n - i + 1) * (i - l)]转移过来(把后面的贡献去掉,就是dp[l][j - (n - i + 1) * (i - l)]),dp[i][j] += ('z' - s[i]) * (dp[l][j - (n - i + 1) * (i - l)]);
2:i位置填的数字小于等于s[i], 那么后面就没影响了,前面所有的l都可以转移:dp[i][j] += sum[j] * (s[i] - 'a), sum[j] 为dp[0][j]到dp[i - 1][j]的和。
代码:
#include <bits/stdc++.h> #define LL long long #define INF 0x3f3f3f3f #define db double #define pii pair<int, int> using namespace std; const int maxn = 2010; const LL mod = 1000000007; LL dp[maxn][maxn], sum[maxn]; char s[maxn]; int main() { int n, k; scanf("%d%d", &n, &k); scanf("%s", s + 1); dp[0][0] = 1; sum[0] = 1; for (int i = 1; i <= n; i++) { for (int j = 0; j <= k; j++) { for (int l = i - 1; l >= 0 && j - (n - i + 1) * (i - l) >= 0; l--) { dp[i][j] = (dp[i][j] + ((LL)('z' - s[i]) * dp[l][j - (n - i + 1) * (i - l)]) % mod) % mod; } dp[i][j] = (dp[i][j] + ((LL)(s[i] - 'a') * sum[j]) % mod) % mod; sum[j] = (sum[j] + dp[i][j]) % mod; } } LL ans = 0; for (int i = 0; i <= n; i++) { ans = (ans + dp[i][k]) % mod; } printf("%lld\n", ans); }