kuangbin专题二十二区间dp总结

专题二十一概率dp和二十二区间dp让我对dp感到了害怕,基本上每一题都不是自主想出来的。今后还得加强对dp的训练。
A - Cake
一开始dp式子想复杂了,隐约感觉到不是那样做。实际上dp式子很简单。
设dp[i][j] 为下标i到j最小的花费,那么
dp[i][j] =min(dp[i][k]+dp[k][j]+cost[i][k]+cost[k][j])
这题也让我真正明白了如何用极角排序了。

#include<bits/stdc++.h>
using namespace std;
#define eps 0
#define _sign(x) ((x)>eps?1:((x)<-eps?2:0))
#define list lst
struct point
{
    int x,y;
    point()
    {
        x=0;
        y=0;
    }
    point(int sx,int sy)
    {
        x=sx;
        y=sy;
    }
};

int xmult(point p1,point p2,point p0)
{
    return (p1.x-p0.x)*(p2.y-p0.y)-(p2.x-p0.x)*(p1.y-p0.y);
}
int is_convex_v2(int n,point *p)
{
    int i,s[3]= {1,1,1};
    for(i=0; i<n&&s[0]&&s[1]|s[2]; i++)
        s[_sign(xmult(p[(i+1)%n],p[(i+2)%n],p[i]))]=0;
    return s[0]&&s[1]|s[2];
}
int n,p;
point list[305];
int cost(int i,int j,int p)
{
    if((i+1)%n==j||(j+1)%n==i)return 0;
    return abs(list[i].x+list[j].x)*abs(list[i].y+list[j].y)%p;
}
double dist(point p1,point p2)
{
    double x=p1.x-p2.x;
    double y=p1.y-p2.y;
    return sqrt(x*x+y*y);
}

bool cmp(point p1,point p2)
{
    int tmp=xmult(list[0],p1,p2);
    if(tmp>0)return 1;
    else if(tmp==0&&dist(list[0],p1)<dist(list[0],p2))return 1;
    return 0;
}
void read()
{
    int i,k;
    point p0;
    cin>>list[0].x>>list[0].y;
    p0.x=list[0].x,p0.y=list[0].y;
    k=0;
    for(i=1;i<n;i++)
    {
        scanf("%d %d",&list[i].x,&list[i].y);
        if(p0.y>list[i].y||(p0.y==list[i].y&&p0.x>list[i].x))
        {
            p0.x=list[i].x;
            p0.y=list[i].y;
            k=i;
        }
    }
    list[k]=list[0];
    list[0]=p0;
    sort(list+1,list+n,cmp);
}
long long dp[305][305];
int main()
{
    while(~scanf("%d %d",&n,&p))
    {
        read();
        if(!(is_convex_v2(n,list)))
            puts("I can't cut.");
        else
        {
            memset(dp,0,sizeof(dp));
            for(int i=n-3; i>=0; i--)
                for(int j=i+2; j<n; j++)
                {
                    dp[i][j]=1e18;
                    for(int k=i+1; k<j; k++)
                        dp[i][j]=min(dp[i][j],dp[i][k]+dp[k][j]+cost(i,k,p)+cost(k,j,p));
                }
            cout<<dp[0][n-1]<<endl;

        }
    }
    return 0;
}

B - Halloween Costumes
设dp[i][j] 为i到j最少换几次衣服。
如果一次也不换,那么dp[i][j]=dp[i+1][j]+1
当存在k,i< k<=j,a[i]=a[k]时,我们可以把i的衣服一直保留着,给k穿,所以dp[i][j]=dp[i][k-1]+dp[k+1][j].

#include<bits/stdc++.h>
using namespace std;
int a[105];
int dp[105][105];
int main()
{
    int T;
    scanf("%d",&T);
    int cas=1;
    while(T--)
    {
        int N;
        scanf("%d",&N);
        for(int i=1;i<=N;i++)scanf("%d",&a[i]);
        memset(dp,0,sizeof(dp));
        for(int i=N;i>=1;i--)
            for(int j=i;j<=N;j++)
            {
                dp[i][j]=dp[i+1][j]+1;
                for(int k=i+1;k<=j;k++)if(a[k]==a[i])
                    dp[i][j]=min(dp[i][j],dp[i][k-1]+dp[k+1][j]);
            }
        printf("Case %d: %d\n",cas++,dp[1][N]);
    }
    return 0;
}

C - Brackets
独立完成的题目之一。。
毕竟比较简单。

#include<iostream>
#include<algorithm>
#include<cstring>
#include<stdio.h>
using namespace std;
const int maxn=105;
int dp[maxn][maxn];
int main()
{
    char s[105];
    while(~scanf("%s",s+1)&&s[1]!='e')
    {
        memset(dp,0,sizeof(dp));
        int n=strlen(s+1);
        for(int i=n;i>=1;i--)
            for(int j=i+1;j<=n;j++)
            {
                dp[i][j]=dp[i+1][j-1];
                if((s[i]=='('&&s[j]==')')||(s[i]=='['&&s[j]==']'))dp[i][j]+=2;
                for(int k=i;k<j;k++)
                    dp[i][j]=max(dp[i][j],dp[i][k]+dp[k+1][j]);
            }
        cout<<dp[1][n]<<endl;

    }

}

D - Coloring Brackets
给出的括号序列就是一个正确的序列,所以首先求出每个括号匹配的另一个括号的下标。
然后设dp[i][j][k][q] 代表i到j序列i涂色的状态为kj涂色的状态为q。分类讨论一下就行了。

#include<bits/stdc++.h>
using namespace std;
const int maxn=705;
const int mod=1e9+7;
char s[maxn];
int p[maxn];
int match[maxn];
void getmatch(int len)
{
    int tot=0;
    for(int i=1; i<=len; i++)
    {
        if(s[i]=='(')p[++tot]=i;
        else
        {
            int idx=p[tot];
            match[idx]=i;
            match[i]=idx;
            tot--;
        }
    }
    //for(int i=1;i<=len;i++)
    //cout<<match[i]<<endl;
}
long long dp[maxn][maxn][4][4];
void dfs(int l,int r)
{
    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(match[l]==r)
    {
        dfs(l+1,r-1);
        for(int i=1; i<=2; i++)
            for(int j=0; j<=2; j++)
                for(int k=0; k<=2; k++)if(k!=i)
                    {
                        dp[l][r][0][i]=(dp[l+1][r-1][j][k]+dp[l][r][0][i])%mod;
                        dp[l][r][i][0]=(dp[l+1][r-1][k][j]+dp[l][r][i][0])%mod;
                    }
    }
    else
    {
        int u=match[l];
        dfs(l,u);
        dfs(u+1,r);
        for(int i=0; i<=2; i++)
            for(int j=1; j<=2; j++)
                for(int k=0;k<=2;k++)if(k!=j)
                dp[l][r][0][i]=((dp[l][u][0][j]*dp[u+1][r][k][i])%mod+dp[l][r][0][i])%mod;
        for(int i=1;i<=2;i++)
            for(int j=0;j<=2;j++)
                for(int k=0;k<=2;k++)
                dp[l][r][i][j]=((dp[l][u][i][0]*dp[u+1][r][j][k])%mod+dp[l][r][i][j])%mod;
    }
}
int main()
{
    scanf("%s",s+1);
    int len=strlen(s+1);
    getmatch(len);
    dfs(1,len);
    long long ans=0;
    for(int i=0; i<=2; i++)
        for(int j=0; j<=2; j++)
            ans=(ans+dp[1][len][i][j])%mod;
    cout<<ans<<endl;
}

E - Multiplication Puzzle
一开始看一份题解递推式子没看懂,换另一份后顿时豁然开朗。
设dp[i][j] 为区间i到j最小消耗,那么dp[i][j]=min(dp[i][k]+dp[k][j]+a[i]*a[k]*a[j]).
为什么是这样的呢,我们只要把k当成最后一个拿走的那张,那么就能理解了。

#include<iostream>
#include<algorithm>
#include<cstring>
#include<stdio.h>
using namespace std;
const int maxn=105;
int a[maxn];
long long dp[maxn][maxn];
int main()
{
    int N;
    scanf("%d",&N);
    for(int i=1;i<=N;i++)
    {
        scanf("%d",&a[i]);
    }
    for(int i=N;i>=1;i--)
        for(int j=i+2;j<=N;j++)
        {
            dp[i][j]=1e18;
            for(int k=i+1;k<=j-1;k++)
                dp[i][j]=min(dp[i][j],dp[i][k]+dp[k][j]+1LL*a[i]*a[k]*a[j]);
            //cout<<i<<' '<<j<<' '<<dp[i][j]<<endl;
        }
        cout<<dp[1][N]<<endl;

}

F - Food Delivery
设dp[i][j][0]为送完i到j这个区间后工作人员在下标为i这个点。dp[i]j][1] 则代表在j这个点。
那么递推就很简单了,在递推过程中,我们要把所有人等待的值都要包括进去,所以得维护一个不高兴度的前缀和。

#include<bits/stdc++.h>
using namespace std;
const int maxn=1005;
const int inf=0x3f3f3f3f;
struct node
{
    int x,b;
    node(int _x,int _b):x(_x),b(_b) {}
    node() {}
    bool operator<(const node&b)const
    {
        return x<b.x;
    }
} nodes[maxn];
int sum[maxn];
int dp[maxn][maxn][2];
int qsum(int l,int r)
{
    return sum[r]-sum[l];
}
int main()
{
    int n,v,x;
    while(~scanf("%d %d %d",&n,&v,&x))
    {
        memset(sum,0,sizeof(sum));
        int x1,b;
        for(int i=1; i<=n; i++)
        {
            scanf("%d %d",&x1,&b);
            nodes[i]=node(x1,b);
        }
        nodes[++n]=node(x,0);
        sort(nodes+1,nodes+1+n);
        for(int i=1; i<=n; i++)
            sum[i]=sum[i-1]+nodes[i].b;
        int idx;
        for(int i=1; i<=n; i++)
            if(nodes[i].x==x&&nodes[i].b==0)
            {
                idx=i;
                break;
            }
        memset(dp,inf,sizeof(dp));
        dp[idx][idx][0]=dp[idx][idx][1]=0;
        for(int i=idx; i>=1; i--)
            for(int j=idx; j<=n; j++)
            {
                if(i==j)continue;
                int tmp=qsum(0,i)+qsum(j,n);
                int tmp2=qsum(0,i-1)+qsum(j-1,n);
                //dp[i][j][0]=1e9;
                //dp[i][j][1]=1e9;
                dp[i][j][0]=min(dp[i][j][0],dp[i+1][j][0]+(nodes[i+1].x-nodes[i].x)*tmp);
                dp[i][j][0]=min(dp[i][j][0],dp[i+1][j][1]+(nodes[j].x-nodes[i].x)*tmp);
                dp[i][j][1]=min(dp[i][j][1],dp[i][j-1][1]+(nodes[j].x-nodes[j-1].x)*tmp2);
                dp[i][j][1]=min(dp[i][j][1],dp[i][j-1][0]+(nodes[j].x-nodes[i].x)*tmp2);
                //cout<<i<<' '<<j<<' '<<dp[i][j][0]<<' '<<dp[i][j][1]<<endl;
            }
        cout<<min(dp[1][n][0],dp[1][n][1])*v<<endl;

    }
}

G - You Are the One
设dp[i][j] 为区间i到j不快乐度最小的值。我们设第i个人第k个走,那么i到i+k-1的人实际上都走了。基于这个我们就可以写出递推式子。
dp[i][j]=min(dp[i+1][i+k-1]+dp[i+k][j]+(k-1)*a[i]+(sum[j]-sum[i+k-1])*k)

#include<bits/stdc++.h>
using namespace std;
const int maxn=105;
int a[maxn];
int sum[maxn];
int dp[maxn][maxn];
int main()
{
    int T;
    scanf("%d",&T);
    int cas=1;
    while(T--)
    {
        memset(sum,0,sizeof(sum));
        int n;
        scanf("%d",&n);
        for(int i=1;i<=n;i++)scanf("%d",&a[i]);
        for(int i=1;i<=n;i++)sum[i]=sum[i-1]+a[i];
        for(int i=n;i>=1;i--)
            for(int j=i;j<=n;j++)
            {
                if(i==j){dp[i][j]=0;continue;}
                dp[i][j]=dp[i+1][j]+sum[j]-sum[i];
                for(int k=2;k<=j-i+1;k++)
                    dp[i][j]=min(dp[i][j],dp[i+1][i+k-1]+dp[i+k][j]+(k-1)*a[i]+k*(sum[j]-sum[i+k-1]));
            }
        printf("Case #%d: %d\n",cas++,dp[1][n]);
    }
    return 0;
}

H - String painter
也是独立完成的一道,看了别人写的突然发现自己写的复杂了。。
设dp[i][j][k]为区间i到j是否已经被刷成k的变成s2的最小次数,0代表还没被刷过。
然后记忆化搜索一下。

#include<bits/stdc++.h>
using namespace std;
const int maxn=105;
char s1[maxn],s2[maxn];
int dp[maxn][maxn][30];
int dfs(int l,int r,int k)
{
    if(l>r)return 0;
    if(l==r)
    {
        if(k==0)return s1[l]!=s2[l];
        return k!=s2[l]-'a'+1;
    }
    if(dp[l][r][k]!=-1)return dp[l][r][k];
    int res=1e9;
    if(k)
    {
        if(k==s2[l]-'a'+1)res=min(res,dfs(l+1,r,k));
        else res=min(res,dfs(l+1,r,k)+1);
    }
    else
    {
        if(s1[l]==s2[l])res=min(res,dfs(l+1,r,k));
        else
            res=min(res,dfs(l+1,r,k)+1);
    }
    for(int i=l+1;i<=r;i++)
    {
        if(s2[i]==s2[l])
        {
            if(k==0)
            {
                if(s1[i]==s2[i]&&s1[l]==s2[l])
                    res=min(res,dfs(l+1,i-1,k)+dfs(i+1,r,k));
                else
                    res=min(res,dfs(l+1,i-1,s2[l]-'a'+1)+dfs(i+1,r,k)+1);
            }
            else
            {
                if(k==s2[i])
                    res=min(res,dfs(l+1,i-1,k)+dfs(i+1,r,k));
                else
                    res=min(res,dfs(l+1,i-1,s2[l]-'a'+1)+dfs(i+1,r,k)+1);
            }
        }
    }
    //cout<<l<<' '<<r<<' '<<k<<' '<<res<<endl;
    return dp[l][r][k]=res;
}
int main()
{
    while(~scanf("%s %s",s1+1,s2+1))
    {
        memset(dp,-1,sizeof(dp));
        int len=strlen(s1+1);
        printf("%d\n",dfs(1,len,0));
    }


}

写博客总结的时候,感觉题目简单了许多,可能事后诸葛亮吧,不论怎样dp还得做更多的题。

猜你喜欢

转载自blog.csdn.net/qq_34921856/article/details/80231309