2019牛客暑期多校训练营(第五场)G-subsequence 1

>传送门<

题意:给你两个数字字符串s,t,求字符串s的子序列比字符串t大的个数

思路:他的题解上写的就是dp的基础练习题,好像的确是这么回事,既然是dp,那么对于定义的状态不同得到的转移方程就不同,写法自然就不一样。这里给出其中一种dp的解法

首先从 s 串中选的数字长度大于 t 串长度,肯定ok,那么我们枚举第一个数字的位置并且用组合数搞一搞就可以了,接下来我们用dp搞定长度与 t 串相等且大于 t 的数字数量即可

设dp[i][j]表示 s的前i个,匹配 t 的前 j 个的种类数

  • if(s[i] == t[j]) dp[i][j] = dp[i -1][j] + dp[i - 1][j - 1];  //等于的话就等于加上与不加上相加
  • if(s[i] < t[j]) dp[i][j] = dp[i - 1][j];  //小于的话就不用算进去了,长度相等时一定会小于,同时可以排除前导零
  • if(s[i] > s[j]) ans+=dp[i - 1][j - 1] * C[n - i][m - j]. //前面匹配j-1的种类数*后面随便选len2-j个,大于后长度相等时一定大于

最后注意计算长度大于t的字符串数量排除前导零就可以了
Code

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod = 998244353;
const int maxn = 3010;

int n, m;
ll f[maxn][maxn], dp[maxn][maxn];
char s[maxn], t[maxn];
void pre()
{
    f[1][1] = f[1][0] = f[0][0] = 1;
    for (int i = 2; i <= 3000; i++){ 
        f[i][0] = 1;
        for (int j = 1; j <= i; j++) f[i][j] = (f[i-1][j-1]+f[i-1][j])%mod;
    }
}

int main() 
{
    pre();
    int T;
    scanf("%d", &T);
    while(T--) {
        scanf("%d %d", &n, &m);
        scanf("%s %s", s + 1, t + 1);
        for(int i = 0; i <= n; i++)
            dp[i][0] = 1;
            
        ll ans = 0;
        //选择当前位数等于t的序列 
        for(int i = 1; i <= n; i++)
            for(int j = 1; j <= min(m, i); j++) {
                dp[i][j] = dp[i-1][j]; //s[i]<t[j]的情况
                if(s[i]==t[j]) dp[i][j] = (dp[i][j] + dp[i-1][j-1])%mod;
                if(s[i]>t[j]) ans = (ans + dp[i-1][j-1]*f[n-i][m-j])%mod;
            }
        //选择当前位数大于t的序列  
        for(int i = 1; i <= n; i++) {
            if(s[i] == '0') continue;
            for(int j = m; j <= n-i; j++)
                ans = (ans + f[n-i][j]) % mod; //i是枚举第一个数,j是枚举后面取几个
        }
        printf("%lld\n", ans);
    }
    return 0;
}
View Code

猜你喜欢

转载自www.cnblogs.com/wizarderror/p/11307389.html