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\n",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\n",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\n",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\n",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\n",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\n",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\n",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\n",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\n",ans[n]);
    }
    return 0;
}
View Code

 

转载于:https://www.cnblogs.com/CJLHY/p/8392289.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值