dp暑假专题 训练记录

Posted Draymonder

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了dp暑假专题 训练记录相关的知识,希望对你有一定的参考价值。

A 回文串的最小划分

 题意:给出长度不超过1000的字符串,把它分割成若干个回文字串,求能分成的最少字串数。

技术分享
#include <iostream>
#include <cstdio>
#include <string.h>
#include <string>
using namespace std;
const int mod = 1e9 + 7;
const int maxn = 1000 + 5;
const int INF = 0x3f3f3f3f;
typedef long long LL;
int dp[maxn];

//根据回文串的特性,两边根据中心对称。于是我们扫描回文串的每个中心,寻找他的回文串,用DP来记录更新
//dp[n] 表示到n为止可以分割出多少个回文串
//如果[Left , Right]是回文串,
//dp[Right] = min(dp[ Right ] , dp[Left-1] + 1);

int main()
{
    int n;
    cin >> n;
    while ( n-- )
    {
        char s[maxn];
        cin >> s+1;
        int L = strlen(s+1);
        for(int i=1;i <= L;i++) dp[i] = i;

        for(int i=1;i <= L;i++)
        {
            for(int j=i,k=i; j <= L && k > 0 ;j++ ,k--)//这种是aabcc形的回文串
            {
                if(s[j] == s[k] )
                    dp[j] = min(dp[j],dp[k-1] +1);
                else
                    break;
            }
            for(int j=i+1,k=i; j <= L && k > 0 ;j++,k--)//这种是aabb形的回文串
            {
                if(s[j] == s[k] )
                    dp[j] = min(dp[j],dp[k-1] +1);
                else
                    break;
            }
        }
        cout<< dp[L]<<endl;
    }
    return 0;
}
A

 

B - LIS变形

题意:给定一串数字,求一个子串满足一下要求:子串的长度是L=2*n+1,前n+1个数字是严格的递增序列,后n+1个数字是严格的递减序列,例如123454321就是满足要求的一个子串,

输出满足要求的最长的L

思路:正着走一遍LIS,再倒着走一遍LIS,a1[i] 表示正着走的  到i的最长递增子序列的长度,a2[i] 表示倒着着走的  到i的最长递增子序列的长度

   //本题 二分 + LIS 需要巩固一下

 

技术分享
#include <iostream>
#include <cstdio>

using namespace std;
const int mod = 1e9 + 7;
const int maxn = 10000 + 5;
const int INF = 0x3f3f3f3f;
typedef long long LL;
int n,len;
int s1[maxn],s2[maxn];
int a1[maxn],a2[maxn];
int B[maxn];
int b_s(int k)
{
    int ans = 0;
    int l = 0,r = len-1; //这里注意 右区间是 len-1
    while ( l <= r)
    {
        int mid = (l+r)/2;
        if(B[mid] >= k ) ans = mid,r = mid-1; //如果k<= B[mid] 往左边查找
        else l = mid +1;
    }
    return ans ;
}

void Lis(int *s,int *a) //a数组存取到i位 的最长递增子序列个数
{
    B[0] = s[0];
    len = 1;
    for(int i=0;i < n;i++)
    {
        if(s[i] > B[len-1])
        {
            B[len++] = s[i];
        }
        else{
            int pos = b_s (s[i]); //二分找s[i]的插入位置
            B[pos] = s[i];
        }
        a[i] = len;
    }

}
int main()
{
    while (cin >> n)
    {
        for(int i=0;i < n;i++){
            cin >> s1[i];
            s2[n-1-i] = s1[i];//把序列倒着读
        }
        Lis(s1,a1), Lis(s2,a2);  //longest increasing substring 最长递增子序列函数
        int ans = 0;
        for(int i=0;i < n;i++)
        {
            ans = max(ans, min(a1[i],a2[n-1-i])); //ans 是 ans 与 当前位 左右最长递增 和 最长递减的较小者
        }
        cout<< 2*ans -1 <<endl;
    }
    return 0;
}
B

 

C - 区间dp or LCS的变形

题意:给你一个字符串, 求最长的回文子序列, 若答案不唯一, 则输出字典序最小的

思路1:区间dp, dp[i][j]表示字符串s[i]到s[j]构成回文子序列学删除的最小字符数,    path[i][j] 字符串s[i]到s[j]可构成的最长回文子序列

技术分享
#include <iostream>
#include <cstdio>
#include <string.h>
#include <string>

using namespace std;
const int mod = 1e9 + 7;
const int maxn = 1000 + 5;
const int INF = 0x3f3f3f3f;
typedef long long LL;
//区间dp dp[i][j] 表示字符串s[i]到s[j]构成回文子序列学删除的最小字符数
//path[i][j] 字符串s[i]到s[j]可构成的最长回文子序列
string path[maxn][maxn];
int dp[maxn][maxn];
char s[maxn];
int main()
{
    while (cin >> s+1)
    {
        int L = strlen(s+1);
        for(int i=L;i >= 1;i--)  //这个查找是从 左下到右上进行的
        {
            dp[i][i] = 0;//i到i不用删除
            path[i][i] = s[i];//i到i的最长回文子序列就是本身
            for(int j=i+1;j <= L;j++)
            {
                if(s[i] == s[j]) //就是 s[i][j]这两个字符相同 可以构成回文串
                {
                    dp[i][j] = dp[i+1][j-1]; //所以这时候 需要删除的就是左下角的
                    path[i][j] = s[i] + path[i+1][j-1] +s[j];//只有这一步是构造回文串的
                }
                else if(dp[i+1][j] > dp[i][j-1])
                {
                    dp[i][j] = dp[i][j-1] + 1;
                    path[i][j] = path[i][j-1];
                }
                else if(dp[i+1][j] < dp[i][j-1])
                {
                    dp[i][j] = dp[i+1][j] + 1;
                    path[i][j] = path[i+1][j];
                }
                else{
                    dp[i][j] = dp[i+1][j] + 1;
                    path[i][j] = min(path[i][j-1], path[i+1][j]);
                }
            }

           /* for(int a = 1;a<=L;a++)
            {
                for(int b= 1;b<=L;b++)
                {
                    cout<< path[a][b]<<" ";
                }
                cout<<endl;
            }
            cout<<"----------" <<endl;  */


        }
        cout << path[1][L] <<endl;
    }
    return 0;
}
C

 思路2:转化为lcs, 用结构体进行保存,一维只长度, 一维指构成的字符串 

我们都知道把一个字符串逆序后和原字符串进最长公共子序列,可以计算出它的最长回文串长度。这题不仅要输出回文串,而且还要求是字典序最小的,所以挺难搞的

设str1是正序字符串,str2是逆序后的字符串
f[i][j].len 表示str1的前i位,str2的前j位,最长公共子串的长度
f[i][j].str 表示str1的前i位,str2的前j位,最长公共子串的最小字典序的字符串

状态转移和正常的LCS差不多,只不过增加了记录字典序最小的字符串

但是最终的f[i][j].str却并不一定是答案,因为计算出来的最长公共子序列不一定就是回文串

例如:
kfclbckibbibjccbej
jebccjbibbikcblcfk

bcibbibc是他们的LCS,但是却不是回文串

但是它的前len/2个一定是回文串的前半部分

技术分享
#include <cstdio>
#include <string>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;
using namespace std;
const int mod = 1e9 + 7;
const int maxn = 1000 + 5;
const int INF = 0x3f3f3f3f;
typedef long long LL;

struct node{
    int len;
    string s;
}dp[maxn][maxn];
char s1[maxn],s2[maxn];

int main ()
{
    while (cin >> s1+1)
    {
        int L = strlen(s1+1);
        strcpy(s2+1,s1+1);
        reverse(s2+1,s2+1+L);

        for(int i=1;i <= L;i++)
        {
            for(int j=1;j <= L;j++)
            {
                if(s1[i] == s2[j]){
                    dp[i][j].len = dp[i-1][j-1].len+1;
                    dp[i][j].s = dp[i-1][j-1].s + s1[i];
                }
                else if(dp[i-1][j].len < dp[i][j-1].len){
                    dp[i][j].len = dp[i][j-1].len;
                    dp[i][j].s = dp[i][j-1].s;
                }
                else if(dp[i-1][j].len > dp[i][j-1].len){
                    dp[i][j].len = dp[i-1][j].len;
                    dp[i][j].s = dp[i-1][j].s;
                }
                else{
                    dp[i][j].len = dp[i-1][j].len;
                    dp[i][j].s = min( dp[i-1][j].s ,dp[i][j-1].s);
                }
            }
        }
        string s = dp[L][L].s;
        int l =dp[L][L].len;
        if( l & 1) //l是奇数,所以字符串长度是偶数
        {
            for(int i=0;i<l/2;i++)
                cout<<s[i];
            for(int i=l/2;i>=0;i--)
                cout<<s[i];
            cout<<endl;
        }
        else{
            for(int i=0;i<l/2;i++)
                cout<<s[i];
            for(int i=l/2-1;i>=0;i--)
                cout<<s[i];
            cout<<endl;
        }

    }
    return 0;
}
C_LCS

 

以上是关于dp暑假专题 训练记录的主要内容,如果未能解决你的问题,请参考以下文章

火热报名中|2020年暑假《嵌入式人工智能》线上专题培训

2017暑假训练记录

斜率优化系列——训练记录

[PLAN]暑期复健训练&其他东西

暑假集训8.7数据结构专题-线段树存直线

蓝桥杯暑假训练题1