区间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;}
题目大意:要参加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;}
题目大意:给一个括号序列,问子序列中最长的合法括号序列长度。
普通的区间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;}
给你一列数,每次可以取走一个数,所得分数的是这个数与其两端的数的乘积。分析可以发现,如果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;}
给一个字符串,可以添加或删除字母,使得字符串回文,问最小花费。
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;}
- 区间DP
- 区间DP
- 区间DP
- 区间DP
- ##区间dp##
- 区间dp
- 区间DP
- 区间dp
- 区间dp
- 区间dp
- 区间dp
- 区间dp
- 区间dp
- 区间dp
- 区间DP
- 区间DP
- 区间DP
- 区间dp
- windows进程间通信
- old_blog 维吉利亚加密与破译
- Castle学习系列(七)---ActiveRecord HQL介绍
- pat1050
- Twisted xmlrpc服务端获得客户端ip的方法
- 区间dp
- 十一种通用滤波算法
- [Cocoa]_[初级]_[vcf文件的生成和导出]
- Hack Dalvik VM解决Android 2.3 DEX/LinearAllocHdr超限
- golang学习之--简单的web网站
- apache/lighttpd/nginx 对比
- MySQL 会话变量 和 系统变量
- RocketMQ与Kafka对比(18项差异)评价版
- android 4种定位原理及实现——1