「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} ightarrow f_{i+1},j)
  • (s_{i+1}=t_{j+1}),往后匹配一位:(f_{i,j} ightarrow f_{i+1,j+1})
  • (s_{i+1}) 为点:(f_{i,j} ightarrow f_{i+1,j-1})

这样够了吗?不够。还漏了一种情况:加的字符无法与 (t) 匹配,但最后都用点删去了,还是合法的转移。

预处理一个 (a_i) 表示串 (s)(i) 往后长度最短、满足转换后为空串的串长度。再加个转移 (f_{i,j} ightarrow 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
", (int) dp[n][m]);
   return 0;
}
原文地址:https://www.cnblogs.com/hongzy/p/13113460.html