我的第一篇题解
题意很明了,求对 T T T 的一种补全方案 (添加字符) 使其与 $ S $ 配对 ( A - T / T - A / C - G / G - C )。
首先将 $ $已有的字符逆过来(即变为配对所需的字符),就可以发现 实际是求 S S S 中和 T T T 相同的子串数量。
于是这道题就做完了做法很明了…
记 f i f_i fi 为以 T T T 中第 i i i 个字符结尾的字串数量 (此时字符下标从 1 开始计算)。
将 S S S 从前往后扫一遍,更新数组。
那么具体如何更新呢?
我们知道,当我们在 S S S 的第 i i i 位中扫到 T T T 中的第 j j j 个字符时,它能与所有先前在 S S S 中以 T T T 中第 j − 1 j-1 j−1 个字符结尾的子串构成合法子串 ,于是据此得到 DP 方程:
f i = f i + f i − 1 ( S i = T j ) f_i = f_i + f_{i-1} (S_i = T_j) fi=fi+fi−1(Si=Tj)
f m f_m fm为最终所求(根据题意, m m m 为字符串 T T T 的长度)。
注意:
- 运算中数据很大,要开高精度。
- DP 数组要从后往前扫,否则会因为在同一位上不合法的重复更新 (即对 S S S 中同一个字符的反复使用更新 DP 数组) 而 WA (可以类比为 0-1 背包和完全背包之间的区别)。
- 在处理
T
T
T 时注意看清代码,不要出现将一个字符变为其对应的字符后又还原的情况(否则会因为
变回原字符转换字符无效卡 BUG)。
代码如下:
#include<bits/stdc++.h>
using namespace std;
const int N = 2002;
int n,m,ans[N][N],len[N];
char a[N],b[N];
int main()
{
cin>>n>>m;
for(int i = 1;i <= n;i++)
cin>>a[i];
for(int i = 1;i <= m;i++)
{
cin>>b[i];
if(b[i] == 'A') b[i] = 'T';
else if(b[i] == 'T') b[i] = 'A';
else if(b[i] == 'C') b[i] = 'G';
else if(b[i] == 'G') b[i] = 'C';
}
for(int i = 1;i <= m;i++) len[i] = 1;
for(int i = 1;i <= n;i++)
for(int j = m;j >= 1;j--)
{
if(a[i] == b[j])
{
if(j == 1)
{
ans[1][1]++;
int pos = 1;
while(ans[1][pos] >= 10)
{
ans[1][pos+1]++;
ans[1][pos] %= 10;
pos++;
len[1] = max(len[1],pos);
}
}
else
{
int maxlen = max(len[j],len[j-1]);
for(int k = 1;k <= maxlen;k++)
ans[j][k] += ans[j-1][k];
for(int k = 1;k <= maxlen;k++)
if(ans[j][k] >= 10)
{
ans[j][k+1]++;
ans[j][k] %= 10;
}
len[j] = maxlen;
if(ans[j][maxlen+1]) len[j] = maxlen+1;
}
}
}
for(int i = len[m];i >= 1;i--)
cout<<ans[m][i];
return 0;
}
后记:
Update:
2021.12.17: 更新了 LaTeX 和内容。
2021.12.18: 更新内容。
2021.12.18: 更新LaTeX。