「CF1366G」 Construct the String「DP」
CF1366G Construct the String
题解
我们把题目中的函数 \(f(s)\) 叫做「转换」。
用一个很朴素的dp:\(f_{i,j}\) 表示 \(s\) 前 \(i\) 位最少删除几个位置使得转换后等于 \(t\) 前 \(j\) 个位置。
边界就是 \(f_{0,0}=0\),转移分几种情况:
- 直接删去 \(i + 1\) :\(f_{i,j}\rightarrow f_{i+1},j\)
- 若 \(s_{i+1}=t_{j+1}\),往后匹配一位:\(f_{i,j}\rightarrow f_{i+1,j+1}\)
- 若 \(s_{i+1}\) 为点:\(f_{i,j} \rightarrow f_{i+1,j-1}\)
这样够了吗?不够。还漏了一种情况:加的字符无法与 \(t\) 匹配,但最后都用点删去了,还是合法的转移。
预处理一个 \(a_i\) 表示串 \(s\) 从 \(i\) 往后长度最短、满足转换后为空串的串长度。再加个转移 \(f_{i,j}\rightarrow f_{i + a_{i+1},j}\)。
考虑正确性,转移显然都合法,现证明最优解能取到。最优方案中最终形成 \(t\) 的“骨架”拎起来,那骨架的每段间隙中没有删除的位置形成的一定是空串。
给一个串 \(S\),选一个子序列转换后是空串,最优选取方案一定是:把字母做 (
,点做)
,遇到(
就选,遇到)
时候有左括号配对就选,最后再把多余的(
退回去。你会发现选出的是一些子段,每一段都是合法的括号序列。
而我们的 \(a_i\) 就表示 \(i\) 开始的最短的括号序列且不能分割成两半。而拼接的情况如(())()
可以通过(())
和()
两次转移实现。
#include <algorithm>
#include <cstring>
#include <cstdio>
using namespace std;
template<typename T>
void chkmin(T &x, const T &y) { if(x > y) x = y; }
const int N = 1e4 + 5, INF = 2 * N;
char s[N], t[N];
short dp[N][N], a[N];
int n, m;
int main() {
scanf("%s%s", s + 1, t + 1);
n = strlen(s + 1); m = strlen(t + 1);
for(int i = 1; i <= n; i ++) {
int y = 0;
for(int j = i; j <= n; j ++) {
if(s[j] == '.') {
if(!y) break ;
else {
y --; a[i] ++;
if(!y) break ;
}
} else {
a[i] ++; y ++;
}
}
if(y) a[i] = 0;
}
for(int i = 0; i <= n; i ++) fill(dp[i], dp[i] + m + 1, INF);
dp[0][0] = 0;
for(int i = 0; i <= n; i ++) {
for(int j = 0; j <= m; j ++) if(dp[i][j] < INF) {
if(i < n) chkmin(dp[i + 1][j], short(dp[i][j] + 1));
if(i < n && j < m && s[i + 1] == t[j + 1]) chkmin(dp[i + 1][j + 1], dp[i][j]);
if(j && i < n && s[i + 1] == '.') chkmin(dp[i + 1][j - 1], dp[i][j]);
if(a[i + 1]) chkmin(dp[i + a[i + 1]][j], dp[i][j]);
}
}
printf("%d\n", (int) dp[n][m]);
return 0;
}