链接:https://ac.nowcoder.com/acm/contest/11256/D
来源:牛客网
题目描述
Given two strings A,BA,B, and little H wants to choose a subsequence from {1,2,⋯ ,∣A∣}{1,2,⋯,∣A∣}(call it aa) and from {1,2,⋯ ,∣B∣}{1,2,⋯,∣B∣}(call it bb) respectively. A scheme is called good iff ∣a∣=∣b∣∣a∣=∣b∣ and ∃i∈{1,2,⋯ ,∣a∣},Aai<Bbi,∀j∈{1,2,⋯ ,i−1},Aaj=Bbj∃i∈{1,2,⋯,∣a∣},Aai<Bbi,∀j∈{1,2,⋯,i−1},Aaj=Bbj. Print the number of good schemes modulo 109+7109+7.
输入描述:
The first line contains a string A (1≤∣A∣≤5000)A (1≤∣A∣≤5000).
The second line contains a string B (1≤∣B∣≤5000)B (1≤∣B∣≤5000).
It's guaranteed that AA and BB only contain lowercase letters.
输出描述:
Output one line only containing one integer, denoting the answer.
示例1
输入
复制
ib
coe
输出
复制
5
说明
For the first case, the 5 good schemes are:
A={1} (i),B={2} (o)A={1} (i),B={2} (o)
A={2} (b),B={1} (c)A={2} (b),B={1} (c)
A={2} (b),B={2} (o)A={2} (b),B={2} (o)
A={2} (b),B={3} (e)A={2} (b),B={3} (e)
A={1,2} (ib),B={2,3} (oe)A={1,2} (ib),B={2,3} (oe)
示例2
输入
复制
banana
apple
输出
复制
273
题意大概是让选择两个等长子序列(可以不连续)构成一个scheme,对于这两个子序列中的一个位置i的元素有(A[a[i]]<B[b[i]]),然后两个子序列前半部分的元素为下标对应的两个串中的字符相等,后半部分任意,问有多少个good scheme。
这实际上就是枚举A和B中的两个位置i,j满足(A[i] < B[j]),然后把前半部分两个串的所有公共子序列个数求出来,乘以后半部分所有的选法加到答案即可。首先看前半部分怎么算。求两个串的公共子序列的个数自然是用DP。设(dp[i][j])表示A串到i,B串到j(不一定取到)的公共子序列的数量。有转移方程:
for(int i = 1; i <= len1; i++) {
for(int j = 1; j <= len2; j++) {
if(s1[i] == s2[j]) {
dp[i][j] = (dp[i - 1][j] + dp[i][j - 1] + 1) % mod;//实际上是dp[i - 1][j] + dp[i][j - 1] - dp[i - 1][j - 1] + dp[i - 1][j - 1] + 1
} else {
dp[i][j] = ((dp[i - 1][j] + dp[i][j - 1]) % mod + mod - dp[i - 1][j - 1]) % mod;
}
}
}
减去dp[i - 1, j - 1]是因为这一部分被重复加了一次。s1[i] == s2[j]时加的1是指公共子序列为s1[i]的这一种情况。之所以要把dp[i - 1, j - 1]是因为这一部分可以和s1[i]组成新的公共子序列。注意有可能减出来负数,要多加模数。
处理出dp数组以后则开始遍历A[i] < B[j]的位置,枚举到一个合法的位置时,就可以计算后半部分的可能的情况数了。由题意。后半部分两个子序列应该长度相等,所以情况数用组合数计算为:(C_n^0C_m^0 + C_n^1C_m^1+...C_n^{min(n, m)}C_m^{min(n, m)})。设(mleq n),则变为(C_n^0C_m^m + C_n^1C_m^{m - 1}+...C_n^mC_m^0),可以看做是从两个分别有n个球和m个球的盒子里总共取m个球的方案数,即(C_{n + m}^m),算出来即可。
#include <bits/stdc++.h>
#define mod 1000000007
using namespace std;
#define int long long
string s1, s2;
long long dp[5005][5005];//dp[i][j]表示s1中到i的子串,s2中到j的子串的公共子序列的数量
int len1, len2;
#define LL long long
#define p 1000000007
const int maxn=2000005;
void extend_gcd(LL a,LL b,LL &x,LL &y){
if(b==0){
x=1,y=0;
return;
}
extend_gcd(b,a%b,y,x);
y-=a/b*x;
}
LL inv[maxn+10];
LL f[maxn+10];
void init(){//阶乘及其逆元打表
f[0]=1;
for(int i=1;i<=maxn;i++){
f[i]=f[i-1]*i%p;
}
LL x,y;
extend_gcd(f[maxn],p,x,y);//先求出f[N]的逆元,再循环求出f[1~N-1]的逆元
inv[maxn]=(x%p+p)%p;
for(int i=maxn-1;i>=1;i--){
inv[i]=inv[i+1]*(i+1)%p;
}
}
LL C(LL n,LL m){
if(n==m||m==0)return 1;
if(m > n) return 0;
return (f[n]*inv[m]%p*inv[n-m]%p)%p;
}
signed main() {
cin >> s1;
cin >> s2;
len1 = s1.size();
len2 = s2.size();
s1 = " " + s1;
s2 = " " + s2;
init();
//dp[i][j]表示s1中到i的子串,s2中到j的子串的公共子序列的数量
for(int i = 1; i <= len1; i++) {
for(int j = 1; j <= len2; j++) {
if(s1[i] == s2[j]) {
//可能出现负数 防爆模
dp[i][j] = (dp[i - 1][j] + dp[i][j - 1] + 1) % mod;
} else {
dp[i][j] = ((dp[i - 1][j] + dp[i][j - 1]) % mod + mod - dp[i - 1][j - 1]) % mod;
}
}
}
long long ans = 0;
for(int i = 1; i <= len1; i++) {
for(int j = 1; j <= len2; j++) {
if(s1[i] < s2[j]) {
long long n = len1 - i, m = len2 - j;
ans = (ans + (dp[i - 1][j - 1] + 1ll) * C(1ll * n + m, 1ll * min(n, m)) % mod) % mod;
}
}
}
cout << ans;
return 0;
}