「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;
}
posted @ 2020-06-13 12:16  hfhongzy  阅读(363)  评论(0编辑  收藏  举报