链接: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)}\)。设\(m\leq 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; }