题目链接:
https://ac.nowcoder.com/acm/contest/885/G
题意:
给你长度分别为
n
,
m
n,m
n,m的字符串
s
1
,
s
2
s1, s2
s1,s2,
1
≤
m
≤
n
≤
3000
1\leq m \leq n\leq 3000
1≤m≤n≤3000,问s1有几个子串严格大于s2.
题解:
设
f
[
j
]
[
k
]
f[j][k]
f[j][k]为当前已经选了
j
j
j个字符, k表示状态
f
[
j
]
[
0
]
f[j][0]
f[j][0]表示选
s
1
s1
s1中的
j
j
j个比
s
2
s2
s2中的
j
j
j个要小的选择个数
f
[
j
]
[
1
]
f[j][1]
f[j][1]表示选
s
1
s1
s1中的
j
j
j个和
s
2
s2
s2中的
j
j
j个相等的选择个数
f
[
j
]
[
2
]
f[j][2]
f[j][2]表示选
s
1
s1
s1中的
j
j
j个比
s
2
s2
s2中的
j
j
j个要大的选择个数
显然,我们需要的答案是,
∑
i
=
m
n
f
[
i
]
[
2
]
\displaystyle\sum_{i = m}^nf[i][2]
i=m∑nf[i][2].
现在来看状态怎么转移:
i
i
i表示当前选到
s
1
s1
s1中第
i
i
i个数
①
s
1
[
i
]
>
s
2
[
j
+
1
]
s1[i] > s2[j + 1]
s1[i]>s2[j+1],
即新加入的数要比
s
2
s2
s2中对应位置的数大,
那么上一个状态相等的就会变大,原来就大的仍然是大于,原来小的现在还是小
f
[
j
+
1
]
[
2
]
+
=
f
[
j
]
[
2
]
+
f
[
j
]
[
1
]
f[j + 1][2] += f[j][2] + f[j][1]
f[j+1][2]+=f[j][2]+f[j][1]
f
[
j
+
1
]
[
0
]
+
=
f
[
j
]
[
0
]
f[j + 1][0] += f[j][0]
f[j+1][0]+=f[j][0]
②
s
1
[
i
]
=
s
2
[
j
+
1
]
s1[i] =s2[j + 1]
s1[i]=s2[j+1],一样大
那么原来怎么样现在还是怎么样
f
[
j
+
1
]
[
2
]
+
=
f
[
j
]
[
2
]
f[j + 1][2] += f[j][2]
f[j+1][2]+=f[j][2]
f
[
j
+
1
]
[
1
]
+
=
f
[
j
]
[
1
]
f[j + 1][1] += f[j][1]
f[j+1][1]+=f[j][1]
f
[
j
+
1
]
[
0
]
+
=
f
[
j
]
[
0
]
f[j + 1][0] += f[j][0]
f[j+1][0]+=f[j][0]
③
s
1
[
i
]
<
s
2
[
j
+
1
]
s1[i] <s2[j + 1]
s1[i]<s2[j+1],同理可以得到
f
[
j
+
1
]
[
0
]
+
=
f
[
j
]
[
0
]
+
f
[
j
]
[
1
]
f[j + 1][0] += f[j][0] + f[j][1]
f[j+1][0]+=f[j][0]+f[j][1]
f
[
j
+
1
]
[
2
]
+
=
f
[
j
]
[
2
]
f[j + 1][2] += f[j][2]
f[j+1][2]+=f[j][2]
④如果当前已有的数个数已经超过
m
m
m了,那也是大于
s
2
s2
s2,如123比13大
这个时候不管原来是小于还是等于还是大于,都会归到
f
[
j
+
1
]
[
2
]
f[j + 1][2]
f[j+1][2],即
f
[
j
+
1
]
[
2
]
+
=
f
[
j
]
[
0
]
+
f
[
j
]
[
1
]
+
f
[
j
]
[
2
]
f[j + 1][2] += f[j][0] + f[j][1] + f[j][2]
f[j+1][2]+=f[j][0]+f[j][1]+f[j][2]
最后需要注意的是,第一位数不能取0,没有意义。
代码:
#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
#define sz sizeof
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> Pair;
const int MAX = 3e3 + 10;
const ll mod = 998244353;
char str[MAX];
int T, n, m;
int s1[MAX], s2[MAX];
ll f[MAX][3];
//设f[j][k]为当前已经选了j个字符, k表示状态
//k=0表示小于
//k=1表示等于
//k=2表示大于
//这里是压掉一维的写法,本来是f[i][j][k],i的意思是当前选到第i位,j和k意思不变,可以先从这个入手
int main() {
scanf("%d", &T);
while (T--) {
memset(f, 0, sz(f));
scanf("%d%d", &n, &m);
scanf("%s", str + 1);
for (int i = 1; i <= n; i++)s1[i] = str[i] - '0';
scanf("%s", str + 1);
for (int i = 1; i <= m; i++)s2[i] = str[i] - '0';
ll ans = 0;
f[0][1] = 1;
for (int i = 1; i <= n; i++)
for (int j = i - 1; j >= 0; j--) {//注意这里一定要从大到小,不然会重复转移
//之前是空的,新加入的是0就跳过
if (j == 0 && s1[i] == 0)continue;
if (j + 1 > m) {//当前串数比s2多
(f[j + 1][2] += f[j][0] + f[j][1] + f[j][2]) %= mod;
}
else {//j + 1 <= m情况
if (s1[i] > s2[j + 1]) {//新加入的数比s2的要大
//相等和大于->大于,小于->小于
(f[j + 1][2] += f[j][2] + f[j][1]) %= mod;
(f[j + 1][0] += f[j][0]) %= mod;
}
else if (s1[i] == s2[j + 1]) {//相等
//都不变
(f[j + 1][2] += f[j][2]) %= mod;
(f[j + 1][1] += f[j][1]) %= mod;
(f[j + 1][0] += f[j][0]) %= mod;
}
else {//要小
//相等和小于->小于,大于->大于
(f[j + 1][0] += f[j][0] + f[j][1]) %= mod;
(f[j + 1][2] += f[j][2]) %= mod;
}
}
}
//统计答案
for (int j = m; j <= n; j++)
(ans += f[j][2]) %= mod;
cout << ans % mod << endl;
}
return 0;
}
本文详细解析了一种用于比较两个字符串子串大小的算法,通过动态规划的方式,递推计算不同状态下的子串数量,最终得出第一个字符串中有多少个子串严格大于第二个字符串。文章通过具体的代码实现,展示了算法的细节和状态转移过程。
348

被折叠的 条评论
为什么被折叠?



