zoukankan      html  css  js  c++  java
  • 字符串类dp的题目总结

    熟练掌握回文串吧,大致有dp或者模拟类的吧

    ①dp+预处理,懂得如何枚举回文串(一)

    ②dp匹配类型的题目(二)

    ③dp+预处理 子串类型 (三)

    ④字符串的组合数(四)

    一:划分成回文串 UVA11584 紫书275     dp+预处理

    题目大意:输入一个字符串,把他划分成尽量少的回文串,能划分成几个?

    思路:定义dp[i]表示i之间最少能弄成几个回文串,然后枚举1~j(j < i),然后在枚举j的时候判断目前是否为回文串,这样复杂度为n^3.因此我们提前用n^2判断好是否为回文串就行了。回文串的方法就是枚举中心,但是枚举中心也是有技巧的!

    lrj老师的代码太牛了!!!第一次见到这么写的!!!具体看代码上的注释吧!!!

     1 // UVa11584 Partitioning by Palindromes
     2 // Rujia Liu
     3 // This code is slightly different from the book.
     4 // It uses memoization to judge whether s[i..j] is a palindrome.
     5 #include<cstdio>
     6 #include<cstring>
     7 #include<algorithm>
     8 using namespace std;
     9 
    10 const int maxn = 1000 + 5;
    11 int n, kase, vis[maxn][maxn], p[maxn][maxn], d[maxn];
    12 char s[maxn];
    13 
    14 int is_palindrome(int i, int j) {
    15   if(i >= j) return 1;
    16   if(s[i] != s[j]) return 0;
    17   //p[i][j]定义的是i到j是否为回文串
    18   if(vis[i][j] == kase) return p[i][j];
    19   vis[i][j] = kase;//我去,还能这么玩,在dp的过程中预处理,然后用kase区分
    20   p[i][j] = is_palindrome(i+1, j-1);
    21   return p[i][j];
    22 }
    23 
    24 int main() {
    25   int T;
    26   scanf("%d", &T);
    27   memset(vis, 0, sizeof(vis));
    28   for(kase = 1; kase <= T; kase++) {
    29     scanf("%s", s+1);
    30     n = strlen(s+1);
    31     d[0] = 0;
    32     for(int i = 1; i <= n; i++) {
    33       d[i] = i+1;
    34       for(int j = 0; j < i; j++)
    35         if(is_palindrome(j+1, i)) d[i] = min(d[i], d[j] + 1);
    36     }
    37     printf("%d
    ", d[n]);
    38   }
    39   return 0;
    40 }
    View Code

    然后我的for循环和lrj老师的不一样

     1 //看看会不会爆int!数组会不会少了一维!
     2 //取物问题一定要小心先手胜利的条件
     3 #include <bits/stdc++.h>
     4 using namespace std;
     5 #define LL long long
     6 #define ALL(a) a.begin(), a.end()
     7 #define pb push_back
     8 #define mk make_pair
     9 #define fi first
    10 #define se second
    11 const int maxn = 1000 + 5;
    12 char atlas[maxn];
    13 int dp[maxn];
    14 bool p[maxn][maxn], vis[maxn][maxn];
    15 
    16 bool dfs(int i, int j){
    17     if (i == j || j < i) return true;
    18     if (atlas[i] != atlas[j]) return false;
    19     if (vis[i][j]) return p[i][j];
    20     vis[i][j] = true;
    21     p[i][j] = dfs(i + 1, j - 1);
    22     return p[i][j];
    23 }
    24 
    25 int main(){
    26     int t; cin >> t;
    27     while (t--){
    28         memset(vis, 0, sizeof(vis));
    29         scanf("%s", atlas + 1);
    30         int len = strlen(atlas + 1);
    31         for (int i = 1; i <= len; i++){
    32             for (int j = 1; j < i; j++){
    33                 p[i][j] = dfs(j, i);
    34             }
    35         }
    36         memset(dp, 0x3f, sizeof(dp));
    37         dp[0] = 0;
    38         for (int i = 1; i <= len; i++){
    39             dp[i] = dp[i - 1] + 1;//我定义目前这个字符单独组成回文串
    40             for (int j = 1; j <= i; j++){
    41                 if (p[i][j]){
    42                     dp[i] = min(dp[i], dp[j - 1] + 1);
    43                 }
    44             }
    45         }
    46         printf("%d
    ", dp[len]);
    47     }
    48     return 0;
    49 }
    View Code

    学习之处:回文串有两种,一种是abba,另一种是cbabc。然后如何枚举这两种本来是应该要分类讨论的,我的想法是枚举i-j,然后用n^2的想法

    二:括号序列 UVA1626 紫书278

    题目大意:给你几个合法的串的定义,只包含()[]这四个符号,加上多少的(、)、[、]能使给定的串变成一个合法的串

    思路:定义dp[i][j]表示从i~j成功匹配所需要添加的最少的符号的个数使之变成合法的串。

    首先我们根据dfs来,可以发现,如果是dfs(i, j),我们要尝试在每一种i~j中进行分割,然后如果i和j是一个合法的串,那么就直接dfs(i-1, j-1),不然就进行分割,然后记得,当最后只有一个符号的时候就假定要再加一个字符即可。

    //看看会不会爆int!数组会不会少了一维!
    //取物问题一定要小心先手胜利的条件
    #include <bits/stdc++.h>
    using namespace std;
    #define LL long long
    #define ALL(a) a.begin(), a.end()
    #define pb push_back
    #define mk make_pair
    #define fi first
    #define se second
    const int inf = 0x3f3f3f3f;
    const int maxn = 100 + 5;
    char s[maxn];
    int dp[maxn][maxn];//从最右边i开始,到j匹配成功的最小的数目
    
    bool match(int i, int j){
        if (s[i] == '(' && s[j] == ')') return true;
        if (s[i] == '[' && s[j] == ']') return true;
        return false;
    }
    
    void display(int i, int j){
        if (i == j) {
            if (s[i] == '(' || s[i] == ')') printf("()");
            else printf("[]");
            return ;
        }
        int ans = dp[i][j];
        if (match(i, j) && ans == dp[i + 1][j - 1]){
            printf("%c", s[i]);
            display(i + 1, j - 1);
            printf("%c", s[j]);
            return ;
        }
        for (int k = i; k <= j - 1; k++){
            if (ans == dp[i][k] + dp[k + 1][j]){
                display(i, k); display(k + 1, j);
                return ;
            }
        }
    }
    
    void readline(char* S) {
      fgets(S, maxn, stdin);
    }
    
    int main(){
        int T;
        readline(s); sscanf(s, "%d", &T); readline(s);
        while (T--){
            readline(s);
            int n = strlen(s) - 2;
            for (int i = 0; i <= n; i++){
                dp[i][i] = 1;
            }
            for (int i = n - 1; i >= 0; i--){
                for (int j = i + 1; j <= n; j++){
                    dp[i][j] = inf;
                    if (match(i, j)) dp[i][j] = min(dp[i][j], dp[i + 1][j - 1]);
                    for (int k = i; k <= j - 1; k++){//如果从k=i+1开始枚举的话,就需要我下面的两句话,但是弊端就是输出的时候要多很多的条件。不过从i开始枚举的话就不需要了
                        //if (match(i, k)) dp[i][j] = min(dp[i][j], dp[i + 1][k - 1] + dp[k + 1][j]);
                        //if (match(k, j)) dp[i][j] = min(dp[i][j], dp[i][k - 1] + dp[k + 1][j - 1]);
                        ///上面两句话有没有无所谓,因为你的j是会枚举到你这些列举的情况的
                        dp[i][j] = min(dp[i][j], dp[i][k] + dp[k + 1][j]);
                    }
                }
            }
            display(0, n);
            printf("
    ");
            if(T) printf("
    ");
            readline(s);
            //printf("%d
    ", dp[0][n]);
        }
        return 0;
    }
    View Code

    学习之处:定义的学习,dp边界的找寻通过dfs来进行的,如何路径输出

    三:http://codeforces.com/contest/477/problem/C    codeforces 272 div1 C

    题目大意:给你字符串s和p,从s删除0~|s|个字符,最多能形成多少个不重叠的字串p

    思路:定义dp[i][j]表示前i个字符删除j个,定义cal(i)表示从第i个开始往前数,删除几个才能组合成一个子串p。

     1 //看看会不会爆int!数组会不会少了一维!
     2 //取物问题一定要小心先手胜利的条件
     3 #include <bits/stdc++.h>
     4 using namespace std;
     5 #define LL long long
     6 #define ALL(a) a.begin(), a.end()
     7 #define pb push_back
     8 #define mk make_pair
     9 #define fi first
    10 #define se second
    11 const int maxn = 2000 + 5;
    12 char s[maxn], p[maxn];
    13 int lens, lenp, pos;
    14 int dp[maxn][maxn];//对当前位置i,删除j个能产生的最大匹配数
    15 
    16 int cal(int ps){
    17     int lp = lenp;
    18     int cnt = 0;
    19     for (int i = ps; i >= 1; i--){
    20         if (s[i] == p[lp]){
    21             lp--;
    22             if (lp == 0){
    23                 pos = i;
    24                 return cnt;
    25             }
    26         }
    27         else cnt++;
    28     }
    29 }
    30 
    31 int main(){
    32     scanf("%s", s + 1); lens = strlen(s + 1);
    33     scanf("%s", p + 1); lenp = strlen(p + 1);
    34 
    35     for (int i = lenp; i <= lens; i++){
    36         int cnt = cal(i);
    37         for (int j = 0; j < i; j++){
    38             dp[i][j] = max(dp[i][j], dp[i - 1][j]);
    39             if (j >= cnt && pos - 1 >= j - cnt){
    40                 dp[i][j] = max(dp[i][j], dp[pos - 1][j - cnt] + 1);
    41             }
    42         }
    43     }
    44     for (int i = 0; i <= lens; i++){
    45         printf("%d%c", dp[lens][i], i == lens ? '
    ' : ' ');
    46     }
    47     return 0;
    48 }
    View Code

    四:http://codeforces.com/contest/476/problem/B  codeforces 272 div2 B

    题目大意:给你两个串,串1是由+或-组合而成,串二是由+、-、?组合而成的。?可以随便改变成+或-,问有多少的概率能使得两个串的+和-数目相等

    思路:先求出串1串二中的+-?的数目,然后让串一的+-减去串二的+-,如果小于0就直接概率为0,反之进行dp即可。定义dp[i][j],表示一共有i+j个?,生成i个+,j个-。

     1 //看看会不会爆int!数组会不会少了一维!
     2 //取物问题一定要小心先手胜利的条件
     3 #include <bits/stdc++.h>
     4 using namespace std;
     5 #define LL long long
     6 #define ALL(a) a.begin(), a.end()
     7 #define pb push_back
     8 #define mk make_pair
     9 #define fi first
    10 #define se second
    11 const int maxn = 100 + 5;
    12 char s1[maxn], s2[maxn];
    13 double dp[maxn][maxn];
    14 
    15 int id(char c){
    16     if (c == '+') return 0;
    17     if (c == '-') return 1;
    18     if (c == '?') return 2;
    19 }
    20 
    21 int main(){
    22     scanf("%s%s", s1, s2);
    23     int len = strlen(s1);
    24     for (int i = 0; i < len; i++){
    25         s1[i] = id(s1[i]);
    26         s2[i] = id(s2[i]);
    27     }
    28     sort(s1, s1 + len);
    29     sort(s2, s2 + len);
    30     int t10 = upper_bound(s1, s1 + len, 0) - lower_bound(s1, s1 + len, 0);
    31     int t11 = upper_bound(s1, s1 + len, 1) - lower_bound(s1, s1 + len, 1);
    32 
    33     int t20 = upper_bound(s2, s2 + len, 0) - lower_bound(s2, s2 + len, 0);
    34     int t21 = upper_bound(s2, s2 + len, 1) - lower_bound(s2, s2 + len, 1);
    35     int t22 = upper_bound(s2, s2 + len, 2) - lower_bound(s2, s2 + len, 2);
    36 
    37     t10 -= t20, t11 -= t21;
    38     if (t10 < 0 || t11 < 0){
    39         printf("0.000000000000
    ");
    40         return 0;
    41     }
    42     double ans = 0;
    43     dp[0][0] = 1.0;
    44     for (int i = 1; i <= t22; i++){//有i个
    45         for (int j = 0; j <= i; j++){//有j个+
    46             int l = i - j;
    47             if (j == 0) dp[j][l] = dp[j][l - 1] * 0.5;
    48             else if (l == 0) dp[j][l] = dp[j - 1][l] * 0.5;
    49             else dp[j][l] = max(dp[j - 1][l] * 0.5 + dp[j][l - 1] * 0.5, dp[j][l]);
    50         }
    51     }
    52     printf("%.12f
    ", dp[t10][t11]);
    53     return 0;
    54 }
    View Code

    五:

    六:

    七:

  • 相关阅读:
    DOS批处理命令-SET命令
    DOS批处理命令-注释
    DOS批处理命令-pause
    DOS批处理命令-goto命令
    [2020.8.3]联想 Z5S(L78071) Magisk ROOT 纯净无推广 一键刷机 ZUI_11.1.171
    [2020.8.3]联想 ZUK Z1 Magisk ROOT 纯净无推广 一键刷机 ZUI_
    [2020.8.3]联想 Z5(L78011) Magisk ROOT 纯净无推广 一键刷机 ZUI_11.1.053
    [2020.8.3]联想 Z6 Pro 5G(L79041) Magisk ROOT 纯净无推广 一键刷机 ZUI_11.3.363
    [2020.8.3]联想 Z6 青春版(L38111) Magisk ROOT 纯净无推广 一键刷机 ZUI_11.1.094
    [2020.8.3]联想 Z6 Pro(L78051) Magisk ROOT 纯净无推广 一键刷机 ZUI_11.1.105
  • 原文地址:https://www.cnblogs.com/heimao5027/p/5746125.html
Copyright © 2011-2022 走看看