牛客多校第五场 G subsequence 1 最长公共子序列/组合数

题意:

给定两个由数字组成的序列s,t,找出s所有数值大于t的子序列。注意不是字典序大。

题解:

首先特判s比t短或一样长的情况。

当s比t长时,直接用组合数计算s不以0开头的,长度大于t的所有子序列数量。

然后再去看s的和t一样长的子序列。

就是在找s和t的公共子序列,并且一旦某一位s比t大了,就不找了,直接用组合数求此种情况下后面的组合方式,一旦某一位s比t小了,也不找了,直接返回0.

组合数要预处理。

#include<iostream>
#include<cstring>
#define MOD 998244353
#define LL long long
using namespace std;
char str1[3005],str2[3005];
LL Cmn[3005][3005]; 
LL dp[3005][3005]; 
int qpow(int base,int n){
    int ans=1;
        while(n){
            if(n%2){
                ans=1LL*ans*base%MOD;
            }
            base=1LL*base*base%MOD;
            n>>=1;
        }
    return ans;
}
//void makerev(){
//    for(int i=1;i<=3000;i++){
//        rev[i]=qpow(i,MOD-2);
//    }
//    return ;
//}
void MakeCmn(){
    memset(Cmn,0,sizeof Cmn);
    Cmn[0][0]=1; 
    for(int n=1;n<=3000;n++){
        Cmn[n][0]=1;
        for(int m=1;m<=n;m++){
            Cmn[n][m]=Cmn[n-1][m-1]+Cmn[n-1][m];
            Cmn[n][m]%=MOD;
        }
    }
//    for(int i=1;i<=3000;i++){
//        for(int j=1;j<=i;j++){
//            printf("从%d里取%d个的方案是%d\n",i,j,Cmn[i][j]);
//        }
//    } 
}
int l1,l2;
int main(){
//    makerev();
    MakeCmn();
    int t;
    scanf("%d",&t);
    while(t--){
        scanf("%d %d",&l1,&l2);
        scanf("%s %s",str1,str2);
        if(l1<l2){
            printf("0\n");
            continue;
        }else if(l1==l2){
            for(int i=0;i<l1;i++){
                if(str1[i]>str2[i])goto B;
                if(str1[i]<str2[i]){
                    printf("0\n");
                    goto A;
                }
            }
            printf("0\n");
            continue;
            B:;printf("1\n");
            A:;continue;
        }
        //第一个串比第二个长的情况
        LL ans=0;
        for(int i=l2+1;i<=l1;i++){
            ans+=Cmn[l1][i];
            ans%=MOD;
            for(int j=0;j<l1;j++){
                if(str1[j]=='0'){
                    ans-=Cmn[l1-1-j][i-1];
                    ans+=MOD;
                    ans%=MOD;
                }
            }
        }
//        printf(">:%d\n",ans);
        dp[0][0] = 1;
        for (int i = 1; i <= l1; i++) {
            dp[i][0] = 1;
            for (int j = 1; j <= min(l2, i); j++) {
                dp[i][j] = dp[i-1][j];
                if (str1[i-1] == str2[j-1]) {
                    dp[i][j] = (0LL+dp[i][j] + dp[i-1][j-1]) % MOD;
                } else if (str1[i-1] > str2[j-1]) {
                    ans = (0LL+ans + dp[i-1][j-1]*Cmn[l1-i][l2-j]) % MOD;
                }
            }
        }
        
        printf("%d\n",ans);
    }
    return 0;
}

 

这道题带来的教训:能用dp,不要用dfs,尤其是当两者思维难度差不太多的时候。

posted @ 2019-08-02 18:43  Isakovsky  阅读(188)  评论(0编辑  收藏  举报