2019牛课多校第五场G-subsequence 1

本文介绍了一种解决G-subsequence问题的算法,通过组合数学和动态规划技术,计算一个字符串中大于另一个字符串的子序列数量。算法分为两部分:一是计算所有大于目标字符串的非零开头子序列;二是计算恰好大于目标字符串且长度相等的子序列。

G-subsequence 1 (dp+组合数学)

题意

给出两个字符串a和b且len(b)≤len(a),len(b)\leq len(a) ,len(b)len(a),aaa中有多少大于bbb的子序列

题解

答案分为两部分
n=len(a),m=len(b)n=len(a),m=len(b)n=len(a),m=len(b)
1、任何非零开头长度大于len(b)len(b)len(b)的子序列都符合条件,这部分可以用组合数学来完成
ans=∑i=1len(a)∑j=mn−i(n−ij)(a[i]≠′0′)ans=\sum_{i=1}^{len(a)}\sum_{j=m}^{n-i}{n-i\choose j}(a[i]\neq {'0'})ans=i=1len(a)j=mni(jni)(a[i]̸=0)
稍微解释一下,为什么是(n−ij){n-i\choose j}(jni)
因为a[i]a[i]a[i]为选择的首位字符,这个子序列的长度要大于m,所以要在剩下的n−in-ini个字符中至少选j,(m≤j≤n−i)j,(m\leq j\leq n-i)j,(mjni)个字符,是组合数。
2、第二部分是长度为len(b)len(b)len(b)且大于bbb的子序列数量
dp[i][j]dp[i][j]dp[i][j]是后iii个字符组成的后缀中,长度为jjj符合条件子序列的个数
状态转移方程如下
dp[i][j]={dp[i−1][j]a[i]&lt;b[j]dp[i−1][j]+dp[i−1][j−1]a[i]=b[j]dp[i−1][j]+(i−1j−1)a[i]&gt;b[j]dp[i][j]= \begin{cases} dp[i-1][j]&amp;a[i]&lt;b[j]\\ dp[i-1][j]+dp[i-1][j-1]&amp;a[i]=b[j]\\ dp[i-1][j]+{i-1\choose j-1}&amp;a[i]&gt;b[j] \end{cases}dp[i][j]=dp[i1][j]dp[i1][j]+dp[i1][j1]dp[i1][j]+(j1i1)a[i]<b[j]a[i]=b[j]a[i]>b[j]
dp[n][m]dp[n][m]dp[n][m]即为这部分的和

#include<bits/stdc++.h>
using namespace std;
typedef long long int LL;
#define sc(x) scanf("%d",&x)
#define scc(x,y) scanf("%d%d",&x,&y)
#define sccc(x,y,z) scanf("%d%d%d",&x,&y,&z)
#define mkp(a,b) make_pair(a,b)
#define F first
#define ep(a) emplace_back(a)
#define S second
#define pii pair<int,int>
#define mem0(a) memset(a,0,sizeof(a))
#define mem(a,b) memset(a,b,sizeof(a))
#define MID(l,r) (l+((r-l)>>1))
#define ll(o) (o<<1)
#define rr(o) (o<<1|1)
const int INF = 0x3f3f3f3f ;
const int maxn = 3000+100;
const double pi = acos(-1.0);
int T,n,m;

typedef long long LL;const LL mod=998244353;
LL comb[maxn][(maxn>>1)+1];
inline LL C(int n,int k){
    if(k>n)return 0;
    k = min(k,n-k);
    return comb[n][k];
}
void solve(){
    for(int i = 0;i<maxn;i++){
        comb[i][0] = 1;
        for(int j = 1;j<=(i>>1);j++){//j<=i/2
            comb[i][j] = (C(i-1,j-1) + C(i-1,j))%mod;
        }
    }
}
char a[maxn],b[maxn];
LL dp[maxn][maxn];
int main(){
    memset(comb,0,sizeof(comb));
    solve();
    cin>>T;
    while(T--){
        scanf("%d%d",&n,&m);
        for(int i=0;i<=n;i++){
            for(int j=0;j<=m;j++)dp[i][j]=0;
        }
        scanf("%s%s",a+1,b+1);
        LL ans=0;
        for(int i=1;i<=n;i++){
            if(a[i]=='0')continue;
            for(int j=m;j<=n-i;j++){
                ans=(ans+C(n-i,j))%mod;
            }
        }
        reverse(a+1,a+1+n);
        reverse(b+1,b+1+m);
        for(int i=1;i<=n;i++){
            for(int j=1;j<=m;j++){
                if(a[i]<b[j])dp[i][j]=dp[i-1][j];
                else if(a[i]==b[j])dp[i][j]=(dp[i-1][j-1]+dp[i-1][j])%mod;
                else dp[i][j]=(dp[i-1][j]+C(i-1,j-1))%mod;
            }
        }
        ans=(ans+dp[n][m])%mod;
        cout<<ans<<endl;
    }
   // system("pause");
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值