区间dp专题解题报告

最近数据结构学傻,先停一下,写会dp冷静一下。

LightOJ - 1422

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
#define pb(x) push_back(x)
#define sca(x) scanf("%d",&x)
#define inf 0x3f3f3f3f

int a[105];
int dp[105][105];
int main()
{
    int t;
    cin>>t;
    int cas=1;
    while(t--)
    {
        int n;
        cin>>n;
        for(int i=1;i<=n;i++)scanf("%d",&a[i]);
        memset(dp,0,sizeof(dp));
        for(int len=1;len<=n;len++)
        {
            for(int i=1;i<=n;i++)
            {
                int j=i+len-1;
                if(j>n)break;
                dp[i][j]=dp[i][j-1]+1;
                for(int k=i;k<j;k++)
                {
                    if(a[k]==a[j])
                    {
                        dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j-1]);
                    }
                }
            }
        }
        printf("Case %d: %d\n",cas++,dp[1][n]);
    }
}
/*
依旧是枚举长度,枚举区间起点终点。
对于新加入的一个点j
如果我们发现在这个区间里边有和j相等的衣服k
那我们就可以不用去管j了。
直接将[k+1,j-1]区间的衣服套上就行。
*/

POJ - 2955

括号匹配。。。

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
#define pb(x) push_back(x)
#define sca(x) scanf("%d",&x)

char s[105];
int dp[105][105];

bool judge(int i,int j)
{
    if(s[i]=='('&&s[j]==')')return true;
    if(s[i]=='['&&s[j]==']')return true;
    return false;
}
int main()
{
    while(~scanf("%s",s))
    {
        if(s[0]=='e')break;
        int len1=strlen(s);
        memset(dp,0,sizeof(dp));
        for(int i=1;i<=len1;i++)
        {
            for(int j=0;j<len1;j++)
            {
                int j1=j+i;
                if(j1>=len1)break;
                if(judge(j,j1))dp[j][j1]=dp[j+1][j1-1]+2;
                for(int k=0;k<j1;k++)
                {
                    dp[j][j1]=max(dp[j][j1],dp[j][k]+dp[k+1][j1]);
                }
            }
        }
        cout<<dp[0][len1-1]<<endl;
    }
}

CodeForces - 149D

类似于分治的思想,非常好的题目丫。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<stack>
using namespace std;
#define pb(x) push_back(x)
#define sca(x) scanf("%d",&x)
#define inf 0x3f3f3f3f
#define LL long long
stack<int>S;
const LL mod=1e9+7;
char s[705];
int pos[705];
LL dp[705][705][3][3];

void solve(int l,int r)
{
    if(l==r)return ;
    if(l+1==r)
    {
        dp[l][r][0][1]=1;
        dp[l][r][1][0]=1;
        dp[l][r][0][2]=1;
        dp[l][r][2][0]=1;
        return ;
    }
    if(pos[l]==r)
    {
        solve(l+1,r-1);
        for(int i=0;i<=2;i++){
            for(int j=0;j<=2;j++){
                if(i!=1)dp[l][r][1][0]+=dp[l+1][r-1][i][j];
                if(j!=1)dp[l][r][0][1]+=dp[l+1][r-1][i][j];
                if(j!=2)dp[l][r][0][2]+=dp[l+1][r-1][i][j];
                if(i!=2)dp[l][r][2][0]+=dp[l+1][r-1][i][j];
                for(int x=0;x<=2;x++){
                    for(int y=0;y<=2;y++){
                        dp[l][r][x][y]%=mod;
                    }
                }
            }
        }
    }
    else if(pos[l]!=r)
    {
        solve(l,pos[l]);
        solve(pos[l]+1,r);
        int tmp=pos[l];
        for(int i=0;i<=2;i++){
            for(int j=0;j<=2;j++){
                for(int x=0;x<=2;x++){
                    for(int y=0;y<=2;y++){
                        if((x==1&&y==1)||(x==y&&y==2))continue;
                        dp[l][r][i][j]+=(dp[l][tmp][i][x]*dp[tmp+1][r][y][j])%mod;
                        dp[l][r][i][j]%=mod;
                    }
                }
            }
        }
    }
}

int main()
{
    scanf("%s",s+1);
    int len=strlen(s+1);
    for(int i=1; i<=len; i++)
    {
        if(s[i]=='(')S.push(i);
        else pos[S.top()]=i,S.pop();
    }
    solve(1,len);
    int n=len;
    LL ans=0;
    for(int i=0;i<=2;i++){
        for(int j=0;j<=2;j++){
            ans+=dp[1][n][i][j];
            ans%=mod;
        }
    }
    printf("%lld\n",ans);
}
/*
本来以为是一个O(n)的dp
结果发现只能处理相邻匹配的情况。其他情况处理不了。

正解:
考虑分治的方法,大区间的解依赖于小区间的解,当得到小区间的解后就
可以合并小区间的解进而得到大区间的解。
注意:题目中所说的相邻的颜色不同,但是相邻的可以都是黑色。
*/

POJ - 1651 

好像是之前省赛选拔赛的题目,当时不会写,现在还不会写。。。不过以后应该会了。。

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
#define pb(x) push_back(x)
#define sca(x) scanf("%d",&x)
#define inf 0x3f3f3f3f

int a[105];
int dp[105][105];

int main()
{
    int n;
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
    }

    for(int len=3;len<=n;len++)
    {
        for(int i=1;i<=n;i++)
        {
            int j=i+len-1;
            if(j>n)break;
            dp[i][j]=inf;
            for(int k=i+1;k<j;k++)
            {
                dp[i][j]=min(dp[i][j],dp[i][k]+dp[k][j]+a[i]*a[k]*a[j]);
            }
        }
    }
    cout<<dp[1][n]<<endl;
}
/*
矩阵连乘变种:
一个区间的最优方式一旦确定,我们就可以利用这个区间的结果。
问题是得到小区间后如何得到大区间的结果。
枚举间断点!!!
*/

ZOJ - 3469 

应该是这几个题目中最难得了。。

问题是。。。很难想到啊

#include<bits/stdc++.h>
using namespace std;
#define LL long long
#define pb(x) push_back(x)
#define sca(x) scanf("%d",&x)
#define rep(i,j,k) for(int i=j;i<=k;i++)
#define N 10005
#define inf 0x3f3f3f3f

struct node
{
    int id,b;
    friend bool operator <(node x,node y)
    {
        return x.id<y.id;
    }
}a[N];
int sum[1005];
int dp[1005][1005][2];
int v;

void solve(int n,int x)
{
    int pos;
    sum[0]=0;
    for(int i=1;i<=n;i++)sum[i]=sum[i-1]+a[i].b;
    for(int i=1;i<=n;i++)
    {
        if(a[i].id==x){
            pos=i;
            break;
        }
    }
    memset(dp,inf,sizeof(dp));
    dp[pos][pos][0]=dp[pos][pos][1]=0;
    for(int i=pos;i>=1;i--)
    {
        for(int j=pos;j<=n;j++)
        {
            if(i==j)continue;
            int del=sum[i-1]+sum[n]-sum[j];
            dp[i][j][0]=min(dp[i][j][0],dp[i+1][j][0]+(a[i+1].id-a[i].id)*(del+a[i].b));
            dp[i][j][0]=min(dp[i][j][0],dp[i+1][j][1]+(a[j].id-a[i].id)*(del+a[i].b));
            dp[i][j][1]=min(dp[i][j][1],dp[i][j-1][0]+(a[j].id-a[i].id)*(del+a[j].b));
            dp[i][j][1]=min(dp[i][j][1],dp[i][j-1][1]+(a[j].id-a[j-1].id)*(del+a[j].b));
            //cout<<i<<" "<<j<<" "<<dp[i][j][0]<<" "<<dp[i][j][1]<<endl;
        }
    }
    printf("%d\n",min(dp[1][n][0],dp[1][n][1])*v);
}
int main()
{
    int n,x;
    while(~scanf("%d%d%d",&n,&v,&x))
    {
        for(int i=1;i<=n;i++)
        {
            sca(a[i].id);
            sca(a[i].b);
        }
        a[n+1].id=x;
        a[n+1].b=0;
        n++;
        sort(a+1,a+1+n);
        solve(n,x);
    }
}
/*
0 2 3 4 5 6
1 3   4 2 1

假设K为所有的愤怒值之和,如果刚开始直接送距离餐厅远的订单的话,
距离与k的乘积就是产生的愤怒值,这样的话愤怒值会增加的很快。
其实就是我们要先处理距离餐厅近的点,再处理距离餐厅较远的点。
证明不太严谨,但是感性的理解一下,应该先送距离近的点。

这个题目根据以上结论倒推的话会简单一点。

假设餐厅在中间的某一个位置
1 2 3 4 7

我最后送的一定是1或者7,因为我处理完小区间之后再处理大区间。
设dp[i][j]为当前区间的结果。
dp[i][j]可以有dp[i+1][j] 和dp[i][j-1]转移过来。
又因为当前状态即可能由i+1点过来也有肯能由j点过来
所以我们用dp[i][j][0]表示转移到左端点,dp[i][j][1]表示转移到右端点。
如果我们只记录当前区间的愤怒值的话,无法得到答案。所以我们每次计算一个区间的时候
加上的是所有订单增加的愤怒值。
所以 dp[i][j][0] 可以由 dp[i+1][j][0] dp[i+1][j][1]转移
dp[i][j][1] 可以由 dp[i][j-1][1] dp[i][j-1][0]转移
而 dp[i+1][j][0]向dp[i][j][1]无用功太多。

*/

HDU - 4283 

这个对我来说也很难,

#include<bits/stdc++.h>
using namespace std;
#define LL long long
#define pb(x) push_back(x)
#define sca(x) scanf("%d",&x)
#define rep(i,j,k) for(int i=j;i<=k;i++)
#define N 10005
#define inf 0x3f3f3f3f

LL a[105];
LL sum[105];
LL dp[105][105];
int main()
{
    int t;
    sca(t);
    int cas=1;
    while(t--)
    {
        int n;
        sca(n);
        sum[0]=0;
        for(int i=1;i<=n;i++)
        {
            scanf("%lld",&a[i]);
            sum[i]=sum[i-1]+a[i];
        }
        memset(dp,0,sizeof(dp));
        for(int len=2;len<=n;len++)
        {
            for(int i=1;i<=n;i++)
            {
                int j=i+len-1;
                if(j>n)break;
                dp[i][j]=inf;
                for(int k=1;k<=len;k++)
                {
                    dp[i][j]=min(dp[i][j],a[i]*(k-1)+(k)*(sum[j]-sum[i+k-1])+dp[i+1][i+k-1]+dp[i+k][j]);
                }
                //cout<<i<<" "<<j<<" "<<dp[i][j]<<endl;
            }
        }
        printf("Case #%d: ",cas++);
        cout<<dp[1][n]<<endl;
    }
}
/*

1 2 3 4 5
2
7
刚开始一直在枚举j的位置,莫名其妙过了样例,后来发现枚举j的位置是不对的。
题目表示有一个类似于栈的屋子,先进的人要后出。
所以对于题目既定的顺序,我们可以做一些调整,就是对于当前区间的第一个人,我们可以任意的调整他在区间的位置。
每次枚举一个区间,对于当前区间的第一个人,他可以选择在任意时刻上台表演,
我们就枚举这个时刻,dp[i][j]表示i-j的最优值,现在我们已经不用考虑区间内部的顺序是如何的,我们只会用到最优值、
i+k到j区间的人因为上台时间延后了k秒,所以再加上一个值就可以了。

*/

HDU - 2476

非常经典的问题,一次改变一个区间,问一个串最少经过几次变成另一个串

很难想到的是先求的一个空串最少经过几次变成另一个串,然后再利用dp得到答案。

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
#define pb(x) push_back(x)
#define sca(x) scanf("%d",&x)
#define inf 0x3f3f3f3f

char s1[105];
char s2[105];
int dp[105][105];
int ans[105];

int main()
{
    while(~scanf("%s%s",&s1,&s2))
    {
        int len=strlen(s1);
        memset(dp,0,sizeof(dp));
        for(int i=0;i<len;i++)dp[i][i]=1;
        memset(ans,0,sizeof(ans));
        for(int i=1;i<=len;i++)
        {
            for(int j=0;j<len;j++)
            {
                int j1=j+i;
                if(j1>=len)break;
                dp[j][j1]=dp[j+1][j1]+1;
                for(int k=j+1;k<=j1;k++)
                {
                    if(s2[j]==s2[k])
                    {
                        dp[j][j1]=min(dp[j][j1],dp[j+1][k]+dp[k+1][j1]);
                    }
                }
                //cout<<j<<" "<<j1<<" = "<<dp[j][j1]<<endl;
            }
        }
        //cout<<dp[0][len-1]<<endl;
        ans[0]=(s1[0]!=s2[0]);
        for(int i=1;i<len; i++)
        {
            if(s1[i]==s2[i])ans[i]=ans[i-1];
            else ans[i]=dp[0][i];
            for(int j=0;j<i;j++)
            {
                ans[i]=min(ans[i],ans[j]+dp[j+1][i]);
            }
        }
        cout<<ans[len-1]<<endl;
    }
}
/*
首先对于b串,计算出空串变成b串最少需要几次区间修改。
dp[i][j]表示一个空串变成i-j需要修改几次、
依旧是枚举区间长度,枚举起点和终点,
对于j来说,第一种情况我们直接修改j
第二种情况,我们在当前区间内找到一个和j相等的点,这样的话我们的j就可以和后边的一个区间合并了。
举个例子
cabcd
假设当前的区间起点是0 区间长度为5
当发现第二个c和第一个c相等时,发现第一个c可以在更改第二个c的时候一起更改、

然后就是对于每一个终点i,我们枚举空串要变成b的位置。
*/

还有一个题目貌似要用到计算几何。。。先不写了。。

猜你喜欢

转载自blog.csdn.net/weixin_40894017/article/details/88848028
今日推荐