牛客多校第五场 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,尤其是当两者思维难度差不太多的时候。