区间dp

来源:互联网 发布:手机打不开数据流量 编辑:程序博客网 时间:2024/05/16 08:11

专题链接:http://acm.hust.edu.cn/vjudge/contest/view.action?cid=70870#overview

A:Cake

题目大意:给你一个多边形,问把多边形切割成三角形的最小花费。


类似于矩阵链乘的思路,在把多边形按顺序编号后,枚举分割点,将多边形分割成一个三角形和两个小的多边形。


#include <cstdio>#include <cstring>#include <iostream>#include <algorithm>using namespace std;const int maxn=330;const int inf=0x7f7f7f7f;struct Point{    int x,y;    Point(int x=0,int y=0):x(x),y(y)    {}    bool operator<(const Point &rhs)    const    {        return x<rhs.x||(x==rhs.x&&y<rhs.y);    }}point[maxn],ch[maxn];Point operator-(const Point &a,const Point &b){    return Point(a.x-b.x,a.y-b.y);}int cross(const Point &a,const Point &b){    return a.x*b.y-a.y*b.x;}int convexHull(int n){    sort(point,point+n);    int m=0;    for(int i=0;i<n;i++)    {        while(m>1&&cross(ch[m-1]-ch[m-2],point[i]-ch[m-2])<=0)  m--;        ch[m++]=point[i];    }    int k=m;    for(int i=n-2;i>=0;i--)    {        while(m>k&&cross(ch[m-1]-ch[m-2],point[i]-ch[m-2])<=0)  m--;        ch[m++]=point[i];    }    if(n>1) --m;    return m;}int cost[maxn][maxn],dp[maxn][maxn];int dynamic(int i,int j){    if(dp[i][j]!=-1)    return dp[i][j];    if(j-i<2)   return dp[i][j]=0;    int ans=inf;    for(int k=i+1;k<=j-1;k++)        ans=min(ans,dynamic(i,k)+dynamic(k,j)+cost[i][k]+cost[k][j]);    return dp[i][j]=ans;}int main(){    int n,p;    //freopen("a.in","r",stdin);    while(scanf("%d%d",&n,&p)!=EOF)    {        for(int i=0;i<n;i++)            scanf("%d%d",&point[i].x,&point[i].y);        if(convexHull(n)!=n)        {            printf("I can't cut.\n");            continue;        }        memset(cost,0,sizeof(cost));        memset(dp,-1,sizeof(dp));        for(int i=0;i<n;i++)            for(int j=i+2;j<n;j++)            {                int x=ch[i].x+ch[j].x;                int y=ch[i].y+ch[j].y;j]                cost[i][j]=(abs(x)*abs(y))%p;            }        printf("%d\n",dynamic(0,n-1));    }    return 0;}


B:Halloween Costumes

题目大意:要参加n场聚会,每场聚会要穿指定样式的衣服,衣服可以一件一件的套在身上,但脱下后就不能继续在用,问至少要多少件衣服。

设dp[i+1][j]为第i+1天到第j天的最优解,那么对于第i天到第j天,最坏情况是穿上第i天的衣服,但如果不穿在(i+1,j)中的k天与i天衣服一样,那么就可以选择i天穿的衣服与第k天的一样,也就是dp[i][j]=min(dp[i][j],dp[i+1][k-1]+dp[k][j]),也就是每件衣服在脱下时才计入结果中。

#include <cstdio>#include <cstring>#include <iostream>using namespace std;const int maxn=105;int dp[maxn][maxn],num[maxn];//dp[i][j]=min(dp[i+1][j]+1,dp[i+1][k-1]+dp[k][j]);int main(){    int t,cas=1;    //freopen("b.in","r",stdin);    scanf("%d",&t);    while(t--)    {        int n;        scanf("%d",&n);        for(int i=0;i<n;i++)    scanf("%d",&num[i]);        memset(dp,0,sizeof(dp));        for(int i=0;i<n;i++)    dp[i][i]=1;        for(int i=n-2;i>=0;i--)            for(int j=i+1;j<n;j++)            {                dp[i][j]=dp[i+1][j]+1;                for(int k=i+1;k<=j;k++) if(num[i]==num[k])                    dp[i][j]=min(dp[i][j],dp[i+1][k-1]+dp[k][j]);            }        printf("Case %d: %d\n",cas++,dp[0][n-1]);    }    return 0;}


C:Brackets

题目大意:给一个括号序列,问子序列中最长的合法括号序列长度。

普通的区间dp,如果第i个和第k个是一个括号对,那么dp[i][j]=max(dp[i][j],dp[i+1][k-1]+dp[k+1][j]+2);

#include <cstdio>#include <cstring>#include <iostream>using namespace std;const int maxn=105;int dp[maxn][maxn];char str[maxn];bool yes(int i,int k){    if(str[i]=='('&&str[k]==')')    return true;    if(str[i]=='['&&str[k]==']')    return true;    return false;}int main(){    freopen("c.in","r",stdin);    while(scanf("%s",str)!=EOF)    {        if(str[0]=='e') break;        memset(dp,0,sizeof(dp));        int n=strlen(str);        for(int i=n-1;i>=0;i--)            for(int j=i+1;j<n;j++)            {                dp[i][j]=dp[i+1][j];                for(int k=i+1;k<=j;k++)                    if(yes(i,k))                        dp[i][j]=max(dp[i][j],dp[i+1][k-1]+dp[k+1][j]+2);            }        printf("%d\n",dp[0][n-1]);    }    return 0;}

D:Coloring Brackets

题目大意:给你一个合法的括号序列,每个括号可以是无色,红,蓝,色的,配对的括号有且只有一个是有颜色的,相邻的两个括号颜色不同,问一共有多少种染色方案。


很有意思的区间dp。一对匹配的括号决定着一个区间,但这个区间的状态仅仅由两个边界是不够的,由于限制的存在,还要有边界上的颜色决定,也就是要增加状态描述,令dp[i][j][ci][cj]代表区间(i,j)且颜色分别为ci,cj时的方案数。那么由加法和乘法定理可以得到转移方程。

#include <cstdio>#include <cstring>#include <iostream>using namespace std;const int maxn=710;const int MOD=1000000007;typedef long long ull;ull dp[maxn][maxn][3][3];int match[maxn],sta[maxn],yes[3][3];char str[maxn];void init(){    for(int i=0;i<3;i++)        for(int j=0;j<3;j++)            if(!i||!j||i!=j)                yes[i][j]=1;            else                yes[i][j]=0;}ull dfs(int l,int r,int cl,int cr){    ull &ans=dp[l][r][cl][cr];    //printf("%d %d %d %d\n",l,r,cl,cr);    if(ans!=-1) return ans;    ans=0;    if(match[l]==r)    {        if((cl==0)^(cr==0))        {            if(l+1==r)  return ans=1;            for(int i=0;i<3;i++)                for(int j=0;j<3;j++)                    if(yes[cl][i]&&yes[cr][j])                    ans=(ans+dfs(l+1,r-1,i,j))%MOD;        }    }    else    {        for(int i=0;i<3;i++)            for(int j=0;j<3;j++)                if(yes[i][j])                ans=(ans+dfs(l,match[l],cl,i)*dfs(match[l]+1,r,j,cr))%MOD;    }    return ans;}int main(){    //freopen("d.in","r",stdin);    init();    while(scanf("%s",str)!=EOF)    {        int cur=0;        int n=strlen(str);        for(int i=0;i<n;i++)            if(str[i]=='(')                sta[cur++]=i;            else                match[sta[--cur]]=i;        memset(dp,-1,sizeof(dp));        ull ans=0;        for(int i=0;i<3;i++)            for(int j=0;j<3;j++)                ans=(ans+dfs(0,n-1,i,j))%MOD;        printf("%lld\n",ans);    }    return 0;}


E:Multiplication Puzzle


给你一列数,每次可以取走一个数,所得分数的是这个数与其两端的数的乘积。分析可以发现,如果k与i,j相乘,那么k一定是区间i,j中最后一个被取走的数,就类似于矩阵链乘,状态转移也很好写了。

#include <cstdio>#include <cstring>#include <iostream>using namespace std;const int maxn=105;const int inf=1<<29;int dp[maxn][maxn],num[maxn];int main(){int n;//freopen("e.in","r",stdin);while(scanf("%d",&n)!=EOF){for(int i=0;i<n;i++)  scanf("%d",&num[i]);memset(dp,0,sizeof(dp));for(int len=3;len<=n;len++)  for(int i=0;i+len-1<n;i++)  {  int j=i+len-1;  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]+num[i]*num[j]*num[k]);  //printf("%d %d %d\n",i,j,dp[i][j]);  }printf("%d\n",dp[0][n-1]);}return 0;}

F:Food Delivery

题目大意:一个人给n个人送外卖,每个人随着时间的递递增不满意度都会增加,问怎样送,才能使得总的不满意度最小。


一开始是想对于(i,j)区间枚举最后一个送到的人,但这样子复杂度要到n^3,对于n=1000显然会超时。但仔细分析可以发现,没必要去枚举,因为:(1)假如外卖店的位置在区间的左侧或右侧,那么直接一直向前去送到最后一个人。(2)假如外卖点的位置在区间的中间,那么最后一个送的人一定是两端的,而不会是中间的,反证,假如是中间的某个人k为最后送的,那么送给k之前,一定送给了端点的人,也就一定要经过k,那么可以在经过的时候直接送给k,而不用折返。

综上,对于某段区间,里面最后送的人一定是区间端点的人,也就将复杂度降到了n^2。

同时要注意的是,dp[i][j]的含义是送完(i,j)之后所有人的不满意度,而不是仅仅(i,j)中的人的不满意度。

#include <cstdio>#include <cstring>#include <iostream>#include <algorithm>using namespace std;const int maxn=1050;const int inf=1<<29;struct Node{    int x,v;    bool operator<(const Node &rhs) const    {        return x<rhs.x;    }}node[maxn];int dp[maxn][maxn][2],sum[maxn];int main(){    int n,x,v;    freopen("f.in","r",stdin);    while(scanf("%d%d%d",&n,&v,&x)!=EOF)    {        for(int i=1;i<=n;i++)            scanf("%d%d",&node[i].x,&node[i].v);        node[n+1].x=x,node[n+1].v=0;        n++;        sort(node+1,node+n+1);        for(int i=1;i<=n;i++)            for(int j=1;j<=n;j++)                dp[i][j][0]=dp[i][j][1]=inf;        sum[0]=0;        for(int i=1;i<=n;i++)            sum[i]=sum[i-1]+node[i].v;        int res=0;        for(int i=1;i<=n;i++)            if(node[i].x==x)            {                res=i;                break;            }        dp[res][res][0]=dp[res][res][1]=0;        for(int i=res;i>=1;i--)            for(int j=res;j<=n;j++)            {                if(i==j)    continue;                int delay=sum[i-1]+sum[n]-sum[j];                dp[i][j][0]=min(dp[i][j][0],dp[i+1][j][0]+(node[i+1].x-node[i].x)*(delay+node[i].v));                dp[i][j][0]=min(dp[i][j][0],dp[i+1][j][1]+(node[j].x-node[i].x)*(delay+node[i].v));                dp[i][j][1]=min(dp[i][j][1],dp[i][j-1][0]+(node[j].x-node[i].x)*(delay+node[j].v));                dp[i][j][1]=min(dp[i][j][1],dp[i][j-1][1]+(node[j].x-node[j-1].x)*(delay+node[j].v));            }        printf("%d\n",min(dp[1][n][0],dp[1][n][1])*v);    }    return 0;}

G:You Are the One


题目大意:n个人,顺序进入会场,越晚进不满意度越高,但可以有一个类似于栈的方式调整次序,问最小不满意度。

同理,枚举区间(i,j)中最后一个出栈的人。

dp[i][j]=min(dp[i][j],dp[i][k-1]+dp[k+1][j]+num[k]*(j-i)+(k-i)*(sum[j]-sum[k])),对于原本在k之前的人,一定在k之前全部调整完,k之后的数,因为k在底,直接当k不存在就好了。

#include <cstdio>#include <cstring>#include <iostream>using namespace std;const int maxn=110;const int inf=1<<29;int dp[maxn][maxn],sum[maxn],num[maxn];int main(){    int t,cas=1;    //freopen("g.in","r",stdin);    scanf("%d",&t);    while(t--)    {        int n;        scanf("%d",&n);        //printf("%d\n",n);        for(int i=1;i<=n;i++)            scanf("%d",&num[i]);        sum[0]=0;        for(int i=1;i<=n;i++)            sum[i]=sum[i-1]+num[i];        for(int i=1;i<=n;i++)            for(int j=1;j<=n;j++)                dp[i][j]=inf;        for(int i=1;i<=n;i++)   dp[i][i]=dp[i][i-1]=dp[i+1][i]=0;        for(int len=2;len<=n;len++)            for(int i=1;i+len-1<=n;i++)            {                int j=i+len-1;                for(int k=i;k<=j;k++)                    dp[i][j]=min(dp[i][j],dp[i][k-1]+dp[k+1][j]+num[k]*(j-i)+(k-i)*(sum[j]-sum[k]));                //printf("%d %d %d\n",i,j,dp[i][j]);            }        printf("Case #%d: %d\n",cas++,dp[1][n]);    }    return 0;}

H:String painter

给出两个等长的字符串,问至少几步可以使a串变为b串。

//留坑,还是不太明白为什么这样搞。

#include <cstdio>#include <cstring>#include <iostream>using namespace std;const int maxn=110;char s1[maxn],s2[maxn];int dp[maxn][maxn],ans[maxn];int main(){    //freopen("h.in","r",stdin);    while(scanf("%s%s",s1,s2)!=EOF)    {        int n=strlen(s1);        for(int i=0;i<n;i++)            for(int j=i;j<n;j++)                dp[i][j]=j-i+1;        for(int len=1;len<=n;len++)            for(int i=0;i+len-1<n;i++)            {                int j=i+len-1;                dp[i][j]=dp[i+1][j]+1;                for(int k=i+1;k<=j;k++)                    if(s2[i]==s2[k])                        dp[i][j]=min(dp[i][j],dp[i+1][k-1]+dp[k][j]);            }        for(int i=0;i<n;i++)        {            ans[i]=dp[0][i];            if(s1[i]==s2[i])            {                if(i)   ans[i]=ans[i-1];                else ans[i]=0;            }            for(int j=0;j<i;j++)                ans[i]=min(ans[i],ans[j]+dp[j+1][i]);        }        printf("%d\n",ans[n-1]);    }    return 0;}


I:Cheapest Palindrome

给一个字符串,可以添加或删除字母,使得字符串回文,问最小花费。

dp[i][j]=min(dp[i+1][j]+cost[str[i]-'a'],dp[i][j-1]+cost[str[j]-'a']),也就是增添或删除首尾字母的代价,以及若首尾字母相同时,则dp[i][j]=min(dp[i][j],dp[i+1][j-1])

#include <cstdio>#include <cstring>#include <iostream>using namespace std;const int maxn=2010;int dp[maxn][maxn];int cost[30];char str[maxn],tmp[3];int main(){    int n,m;    //freopen("i.in","r",stdin);    while(scanf("%d%d",&m,&n)!=EOF)    {        scanf("%s",str);        for(int i=0;i<m;i++)        {            int a,b;            scanf("%s%d%d",tmp,&a,&b);            cost[tmp[0]-'a']=min(a,b);        }        memset(dp,0,sizeof(dp));        for(int len=2;len<=n;len++)            for(int i=0;i+len-1<n;i++)            {                int j=i+len-1;                dp[i][j]=min(dp[i+1][j]+cost[str[i]-'a'],dp[i][j-1]+cost[str[j]-'a']);                if(str[i]==str[j])                    dp[i][j]=min(dp[i][j],dp[i+1][j-1]);            }        printf("%d\n",dp[0][n-1]);    }    return 0;}


0 0
原创粉丝点击