字符串类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\n", 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\n", 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("\n");
        if(T) printf("\n");
        readline(s);
        //printf("%d\n", 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 ? '\n' : ' ');
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\n");
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\n", dp[t10][t11]);
53     return 0;
54 }
View Code

 

五:

 

 

六:

 

七:

转载于:https://www.cnblogs.com/heimao5027/p/5746125.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值