2019牛客暑期多校训练营(第五场)G subsequence 1【DP】
题意:
给你两个数字n, m, 分别是字符串s,t各自的长度,请问有多少个比t字典序大的s的子序列
题目链接:
https://ac.nowcoder.com/acm/contest/885/G
题解:
要选字典序大的,那么我们有两种选取情况
- 选长度比他长的,肯定是可以的,我们可以枚举第一个字符的位置,这样后面的数字随便取,只要总长度超过m肯定没问题,则说明后面至少要取m个,可以用组合数学求
- 选长度和他相等的,那么就要进行DP。dp[i][j] 表示 s串中前i个数字选择了j个比 t串前j个数字大的方案数; f[i][j] 表示 s串中前i个数字选择了j个与t串前j个数字一样大的方案数; 状态转移为:当 s 串第i个数字没被选的时候,d[i][j] = d[i-1][j],f[i][j] = f[i - 1][j];当 s 串第i个数字被选的时候,dp[i][j] += dp[i-1][j-1],如果s[i] > t[j],那么d[i][j] += f[i-1][j-1],如果s[i] = t[j],那么f[i][j] += f[i-1][j-1]。
AC_code:
#include<bits/stdc++.h>
using namespace std;
#define maxn 3005
#define mod 998244353
char s[maxn], t[maxn];
int c[maxn][maxn], dp[maxn][maxn], f[maxn][maxn];
void add(int &x, int y) {
x = (x + y) % mod;
}
void init() { //组合数预处理
/*
c[i][j] 代表 i个数里面取至少j个数字的方案数
*/
c[0][0] = 1;
for(int i = 1; i <= 3000; i++) {
c[i][0] = c[i][i] = 1;
for(int j = 1; j < i; j++) {
add(c[i][j], c[i-1][j-1]+c[i-1][j]);
}
}
for(int i = 1; i<= 3000; i++) {
for(int j = i; j >= 0; j--) {
add(c[i][j], c[i][j+1]);
}
}
}
int main() {
int T, n, m;
init();
scanf("%d", &T);
while(T--) {
scanf("%d %d", &n, &m);
scanf("%s %s", s+1, t+1);
int ans = 0;
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= m; j++) {
dp[i][j] = f[i][j] = 0;
}
}
for(int i = 1; i <= n; i++) {
f[i-1][0] = 1;
if(s[i] != '0' && (n-i+1 > m)) { //枚举首个字母的位置 且不为0
add(ans, c[n-i][m]);//后面的数字随便取
}
for(int j = 1; j <= min(i, m); j++) {
add(f[i][j], f[i-1][j]);
add(dp[i][j], dp[i-1][j]);
add(dp[i][j], dp[i-1][j-1]);
if (s[i] > t[j]) {
add(dp[i][j], f[i-1][j-1]);
} else if (s[i] == t[j]) {
add(f[i][j], f[i-1][j-1]);
}
}
}
add(ans, dp[n][m]);
printf("%d\n", ans);
}
}