• kuangbin 区间dp


    A - Cake

     题目大意:给你一个n个顶点(n<=100)的多边形和每两个点连边的消耗,让你求把这个多边形全部切成三角形所需要的最小消耗,如果这个多边形为凹多边形则输出无解。

    思路:先求一个凸包,看凸包里的点是不是n个,不是n个输出无解,求完凸包之后,点都是按顺时针排的,我们用dp[ i ][ j ]表示,i 到 j 的折线和 i 连 j的直线围成的多边形

    的最小消耗。

    状态转移方程:dp[ i ][ j ]=min( dp[ i ][ j ] , dp[ i ][ k ]+dp[ k ][ j ]+cost[ i ][ k ]+cost[ k ][ j ] )   ( i<k<j )

    则dp[ 0 ][ n-1 ]即为答案。

    #include<bits/stdc++.h>
    using namespace std;
    const int N=305;
    const int inf=0x3f3f3f3f;
    int n,tot,dp[N][N],cost[N][N],mod;
    struct point
    {
        int x,y;
        point(int _x=0,int _y=0){x=_x; y=_y;}
        point operator -(const point &rhs)const
        {
            return point(x-rhs.x,y-rhs.y);
        }
    }p[N],cp[N];
    typedef point vec;
    int cross(const vec &a,const vec &b)
    {
        return (a.x*b.y)-(a.y*b.x);
    }
    int dis(point a,point b)
    {
        return (a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y);
    }
    bool cmp(const point &a,const point &b)
    {
        int z=cross(a-p[0],b-p[0]);
        if(z>0||(z==0 && dis(p[0],a)<dis(p[0],b)))
            return 1;
        else return 0;
    }
    void get_cp()
    {
        int k=0; tot=0;
        for(int i=0;i<n;i++)
            if(p[i].y<p[k].y || p[i].y==p[k].y && p[i].x<p[k].x) k=i;
        swap(p[0],p[k]);
        sort(p+1,p+n,cmp);
        tot=2,cp[0]=p[0];cp[1]=p[1];
        for(int i=2;i<n;i++)
        {
            while(tot>1 && cross(cp[tot-2]-cp[tot-1],p[i]-cp[tot-1])>0) tot--;
            cp[tot++]=p[i];
        }
    }
    void init()
    {
        memset(dp,-1,sizeof(dp));
        memset(cost,0,sizeof(cost));
    }
    int solve(int l,int r)
    {
        if(dp[l][r]!=-1) return dp[l][r];
        if(r-l<=2) return 0;
        dp[l][r]=inf;
        for(int k=l+1;k<=r-1;k++)
            dp[l][r]=min(solve(l,k)+solve(k,r)+cost[l][k]+cost[k][r],dp[l][r]);
        return dp[l][r];
    }
    int main()
    {
        while(scanf("%d%d",&n,&mod)!=EOF)
        {
            for(int i=0;i<n;i++) scanf("%d%d",&p[i].x,&p[i].y);
            get_cp();
            if(tot<n)
            {
                puts("I can't cut.");
                continue;
            }
            init();
            for(int i=0;i<n;i++)
                for(int j=i+2;j<n;j++)
                    cost[i][j]=cost[j][i]=abs(cp[i].x+cp[j].x)*abs(cp[i].y+cp[j].y)%mod;
    
            printf("%d
    ",solve(0,n-1));
        }
    
        return 0;
    }
    View Code

    B - Halloween Costumes

    题目大意:有一个人去参加n场派对,不同种类的派对需要的衣服是不一样的,问你他最少需要多少件衣服,他最多可以套无限个衣服,脱掉的衣服不能重新利用。

    思路:用dp[ i ][ j ]表示从第 i 个派对开始 到 第 j 个派对结束最少需要多少衣服。

    状态转移方程:

    dp[ i ][ j ]=dp[ i ][ j-1 ] + 1

    dp[ i ][ j ]=min( dp[ i ][ j ] , dp[ i ][ k ]+dp[ k+1 ][ j-1 ])  ( i <k<j  || 第k个派对种类和第 j 种相同)

    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    using namespace std;
    const int N=101;
    int dp[N][N],n,a[N];
    int solve(int l,int r)
    {
        if(dp[l][r]!=-1) return dp[l][r];
        if(l==r) return 1;
        if(l>r) return 0;
        dp[l][r]=solve(l,r-1)+1;
        for(int i=l;i<r;i++)
        {
            if(a[i]!=a[r]) continue;
            dp[l][r]=min(dp[l][r],solve(l,i)+solve(i+1,r-1));
        }
        return dp[l][r];
    }
    int main()
    {
        int T; scanf("%d",&T);
        for(int cas=1;cas<=T;cas++)
        {
            memset(dp,-1,sizeof(dp));
            scanf("%d",&n);
            for(int i=1;i<=n;i++) scanf("%d",&a[i]);
            int ans=solve(1,n);
            printf("Case %d: %d
    ",cas,ans);
        }
        return 0;
    }
    View Code

    C - Brackets

     题目大意:给你一个括号的序列,问你最多的一种匹配里面由多少个括号。

    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    using namespace std;
    const int N=105;
    int dp[N][N],n;
    char s[N];
    int solve(int i,int j)
    {
        if(dp[i][j]!=-1) return dp[i][j];
        if(i>=j) return 0;
        dp[i][j]=max(solve(i+1,j),solve(i,j-1));
        for(int k=i;k<j;k++)
        {
            if(s[j]==')' && s[k]=='(')
                dp[i][j]=max(dp[i][j],solve(i,k-1)+solve(k+1,j-1)+1);
            if(s[j]==']' && s[k]=='[')
                dp[i][j]=max(dp[i][j],solve(i,k-1)+solve(k+1,j-1)+1);
        }
        return dp[i][j];
    }
    int main()
    {
        while(scanf("%s",s+1)!=EOF)
        {
            if(s[1]=='e') break;
            n=strlen(s+1);
            memset(dp,-1,sizeof(dp));
            printf("%d
    ",2*solve(1,n));
        }
        return 0;
    }
    View Code

    D - Coloring Brackets

     题目大意:给你一串匹配号的括号,让你给这些括号染色,染色的规则如下:

    1.每个括号只能染红色,蓝色,或者不然色。

    2.匹配的括号只能有一个被染色。

    3.相邻的括号被染的颜色不能相同,但可以都不染色。

    思路:用dp[ i ][ j ][ p ][ q ]表示区间 i 到 j 之间且第 i 个括号的染色情况为p ,第 j 个括号的染色情况为q 的方案数。

    状态转移方程分i,j 是否匹配,如果匹配从i+1 到  j-1的区间转移过来。 否则在i 到 j 之间找与与i 匹配的k 进行转移。

    #include<bits/stdc++.h>
    using namespace std;
    const int N=705;
    const int mod=1e9+7;
    int a[N],n;
    long long f[N][N][3][3];
    char s[N];
    stack<int> st;
    long long dp(int i,int j,int p,int q)
    {
        if(f[i][j][p][q]!=-1) return f[i][j][p][q];
        if(i+1==j)
        {
            if((p==0 || q==0) && (p!=0 || q!=0)) return f[i][j][p][q]=1;
            else return f[i][j][p][q]=0;
        }
        f[i][j][p][q]=0;
        if(a[i]==a[j])
        {
            if(p!=0 && q!=0 || p==0 && q==0) return 0;
            for(int u=0;u<=2;u++)
                for(int v=0;v<=2;v++)
                    if((u!=p || (!u && !p)) && (q!=v || (!q && !v)))
                        f[i][j][p][q]=(f[i][j][p][q]+dp(i+1,j-1,u,v))%mod;
        }
        else
        {
            for(int k=i+1;k<j;k++)
            {
                if(a[k]!=a[i]) continue;
                for(int u=0;u<=2;u++)
                    for(int v=0;v<=2;v++)
                        if(u!=v || (!u && !v))
                            f[i][j][p][q]=(f[i][j][p][q]+(dp(i,k,p,u)*dp(k+1,j,v,q))%mod)%mod;
                break;
            }
        }
        return f[i][j][p][q];
    }
    int main()
    {
        memset(f,-1,sizeof(f));
        scanf("%s",s+1);
        n=strlen(s+1);
        int cnt=0;
        for(int i=1;i<=n;i++)
        {
            if(s[i]=='(') st.push(i);
            else
            {
                int cur=st.top();
                cnt++; st.pop();
                a[cur]=a[i]=cnt;
            }
        }
        long long ans=dp(1,n,0,1)+dp(1,n,1,0)+dp(1,n,0,2)+dp(1,n,2,0)+dp(1,n,0,0)+dp(1,n,1,1)+dp(1,n,1,2)+dp(1,n,2,1)+dp(1,n,2,2);
        printf("%lld
    ",ans%mod);
        return 0;
    }
    View Code

    E - Multiplication Puzzle

     题目大意:给你n个数字,每次取不是端点的一个数字a[ i ],总消耗加上a[ i-1 ]*a[ i ]*a[ i+1 ],直到最后剩两个数字,问你总消耗最少是多少。

    思路:对于一个区间来说最后剩下的肯定是两端的数字,那么我们用dp[ i ][ j ]表示区间 i 到 j最小的消耗。枚举这个区间最后被取走的数进行状态转移。 

    dp[ i ][ j ]=min(dp[ i ][ j ] , dp[ i ][ k-1]+dp[ k+1 ][ j ]+a[ i ]*a[ j ]*a[ k ])

    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    using namespace std;
    const int N=105;
    const int inf=0x3f3f3f3f;
    int n,f[N][N],a[N];
    int dp(int i,int j)
    {
        if(f[i][j]!=-1) return f[i][j];
        if(j-i<=1) return 0;
        f[i][j]=inf;
        for(int k=i+1;k<=j-1;k++)
            f[i][j]=min(f[i][j],a[i]*a[k]*a[j]+dp(i,k)+dp(k,j));
        return f[i][j];
    }
    int main()
    {
        while(scanf("%d",&n)!=EOF)
        {
            memset(f,-1,sizeof(f));
            for(int i=1;i<=n;i++)
                scanf("%d",&a[i]);
            printf("%d
    ",dp(1,n));
        }
        return 0;
    }
    View Code

    F - Food Delivery

     题目大意:在x轴上有很多人同时点了外卖,给你他们的位置,再告诉你餐厅的位置,送外卖的人走1单位的长度 需要v秒,每个人等t秒 仇恨值就会增加 t * b[ i ],

    让你求除最小的仇恨值。

    思路:我们先将餐厅也作为一个点加入到点的集合中,按坐标的大小进行排序。

    用dp[ i ][ j ][ 0 ]表示送完i 到 j的人,并最后在 i 的最小仇恨值, 用dp[ i ][ j ][ 1 ]表示送完i 到 j的人,并最后在 j 的最小仇恨值。

    从餐厅的为止向外进行dp状态转移。

    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    using namespace std;
    const int N=1005;
    const int inf=0x3f3f3f3f;
    int dp[N][N][2],n,v,pos,sum[N];
    struct node
    {
        int x,b;
        bool operator < (const node &rhs)const
        {
            return x<rhs.x;
        }
    }p[N];
    int cal(int l,int r)
    {
        if(l>r) return 0;
        return sum[r]-sum[l-1];
    }
    int main()
    {
        while(scanf("%d%d%d",&n,&v,&pos)!=EOF)
        {
            memset(dp,inf,sizeof(dp));
            for(int i=1;i<=n;i++)
                scanf("%d%d",&p[i].x,&p[i].b);
            n++; p[n].x=pos,p[n].b=0;
            sort(p+1,p+1+n);
            for(int i=1;i<=n;i++)
            {
                if(p[i].x==pos)
                {
                    pos=i;
                    break;
                }
            }
            for(int i=1;i<=n;i++)
                sum[i]=sum[i-1]+p[i].b;
            dp[pos][pos][0]=dp[pos][pos][1]=0;
            for(int i=pos;i>=1;i--)
            {
                for(int j=pos;j<=n;j++)
                {
                    int daley=cal(1,i-1)+cal(j+1,n);
                    if(i==j) continue;
                    dp[i][j][0]=min(dp[i][j][0],dp[i+1][j][0]+(p[i+1].x-p[i].x)*(daley+p[i].b));
                    dp[i][j][0]=min(dp[i][j][0],dp[i+1][j][1]+(p[j].x-p[i].x)*(daley+p[i].b));
                    dp[i][j][1]=min(dp[i][j][1],dp[i][j-1][0]+(p[j].x-p[i].x)*(daley+p[j].b));
                    dp[i][j][1]=min(dp[i][j][1],dp[i][j-1][1]+(p[j].x-p[j-1].x)*(daley+p[j].b));
                }
            }
            printf("%d
    ",min(dp[1][n][0],dp[1][n][1])*v);
        }
        return 0;
    }
    View Code

    G - You Are the One

     题目大意:有n个排好队的人要上台表演,第k个人上台表演的消耗为( k-1 )*d[ k ]。 现在有一个栈,你可以暂时把人压入栈中,之后再出来,问你最小的消耗为多少。

    思路:用dp[ i ][ j ]表示,从i个人开始上台,第j个人结束的最小消耗。

    对于每个区间,我们枚举第i个人的出场顺序,那么区间就被分成了两个,这样来进行状态转移。

    #include<bits/stdc++.h>
    using namespace std;
    const int inf=0x3f3f3f3f;
    const int N=105;
    int f[N][N],n,sum[N],a[N];
    int dp(int i,int j)
    {
        if(i>j) return 0;
        if(f[i][j]!=-1) return f[i][j];
        f[i][j]=inf;
        for(int k=1;k<=j-i+1;k++)
        {
            f[i][j]=min(f[i][j],dp(i+1,i+k-1)+dp(i+k,j)+k*(sum[j]-sum[i+k-1])+a[i]*(k-1));
        }
        return f[i][j];
    }
    int main()
    {
        int T; scanf("%d",&T);
        for(int cas=1;cas<=T;cas++)
        {
            memset(f,-1,sizeof(f));
            memset(sum,0,sizeof(sum));
            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];
            printf("Case #%d: %d
    ",cas,dp(1,n));
        }
        return 0;
    }
    View Code

    H - String painter

     题目大意:给你两个由小写字母构成的字符串,你每次可以将第一个串的一个连续区间改成同一个小写字母,问你最少需要多少次改变,就能将第一个串变成第二个。

    思路:直接将第一个串变成第二个非常困难, 我们先考虑一个空白的串变成第二个串,这样的问题区间dp就可以解决,然后通过这个对第一个串再进行一次dp就能求出答案。

    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    using namespace std;
    const int N=105;
    const int inf=0x3f3f3f3f;
    int f[N][N],n,ans[N];
    char a[N],b[N];
    int dp(int i,int j)
    {
        if(f[i][j]!=-1) return f[i][j];
        if(i==j) return 1;
        if(i>j) return 0;
        f[i][j]=dp(i,j-1)+1;
        for(int k=i;k<j;k++)
        {
            if(b[k]!=b[j]) continue;
            f[i][j]=min(f[i][j],dp(i,k)+dp(k+1,j-1));
        }
        //printf("(%d,%d) %d
    ",i,j,f[i][j]);
        return f[i][j];
    }
    int main()
    {
        while(scanf("%s%s",a+1,b+1)==2)
        {
            memset(f,-1,sizeof(f));
            memset(ans,0,sizeof(ans));
            int n=strlen(a+1);
            for(int i=1;i<=n;i++) ans[i]=dp(1,i);
            for(int i=1;i<=n;i++)
            {
                if(a[i]==b[i]) ans[i]=ans[i-1];
                else
                {
                    for(int j=1;j<i;j++)
                        ans[i]=min(ans[i],ans[j]+dp(j+1,i));
                }
            }
            printf("%d
    ",ans[n]);
        }
        return 0;
    }
    View Code
  • 相关阅读:
    bzoj3576 [Hnoi2014]江南乐
    codeforces 438D The Child and Sequence
    bzoj1926 [Sdoi2010]粟粟的书架
    bzoj1185 [HNOI2007]最小矩形覆盖
    bzoj1069 [SCOI2007]最大土地面积
    bzoj4569 [Scoi2016]萌萌哒
    西西爆难积分
    概率问题-距离的期望
    各类平均与数列极限
    三角恒等式
  • 原文地址:https://www.cnblogs.com/CJLHY/p/8392289.html
Copyright © 2020-2023  润新知