区间DP
来源:互联网 发布:无人机自动降落算法 编辑:程序博客网 时间:2024/06/05 19:09
区间DP
指一个序列上的问题,可以通过将区间分割更小的区间进行求解,在合并得到答案!
hdu 4283 You Are the One
题意:有一队人排好了队出场,每个人都有排队指数,对管理者的意见值=(出场序号-1)*排队指数,现在有一个栈,可以用来调整出场次序,可以将队列中首个人,安排入栈,第一个出场的人可以是栈首人,也可是队首人,问调整次序后意见值和最小值。
·题目太切合实际,有是一道新颖的实际应用题!
·题目非常容易陷入模拟其中情节过程,而越离正确的解题算法越远了!
正确的模拟循序是:
·第一个人,他出场的次序可能为1…N;而定下他的出场次序,我们就将问题切割成小的相似问题!如果他的次序为k,那么队列中第2到第k个人的次序为1到k-1,
第k+1个人到第N个人的出场次序为k+1到N;剖分成两个子问题!
这就是区间dp的标志!
·如果我们太关注那个栈,就会被吸引进找到第一个出场的人,进而陷入无法将问题切割成小的相似的子问题!
·编程步骤:·预处理好:1)区间长度为1的人的N种出场情况!
2)其它长度的F值全部为INF!
3)为便于后面F的状态转移正确性:
F[i+1][i]全部为0!
·DP过程:
第1重循环:将len从2遍历到N;
第2重循环:将pos从1遍历到N+1-len;
第3重循环:将区间首元素从1遍历到N+1-len;
第4重循环:状态转移过程:首元素i能够选的全部出场次序一
一比较每种的意见值,选出最小的。
·编程注意:1)不要直接复制题目给的输入数据,应该是字体不同,编译器Running error!
2) 用来dp的F数组不能每维长度刚刚开100,这里不行,报Runtime Error(ACCESS_VIOLATION),数组越界!
3) 时间复杂度上,虽然是4重循环,但是N最大为100;经过计算循环计算次数为N^4/12,N=100下,计算850万次;可支撑100多次N=100的case的测试,不用担心!提交花费时间:156ms!
4) 编程时:可以不用做预处理,直接在计算到该F时,再在第四重前,
将F[i+1][i]=0,F[i][last]=INF;
但我错误理解了F的含义,F[1][N][1]是我们的答案;脑袋抽了
一下,将F理解成了1到N个人,第1个人先出的最小意见
值,而F真实含义表示1到N个人中最先出场的人序号为1最
小意见值!所以导致后面做了一次for循环判断F[1][N][1…N]
之间的大小,其它F[1][N][2…N]不会与循环,都是0!答案是0!
/**HDU 4283---You Are the One*/#include <iostream>#include <stdio.h>#include <string.h>#include <math.h>#include <algorithm>#define INF 1000000000using namespace std;int F[110][110][110];int D[110];int main(){ int T;scanf("%d",&T); int kase=0; while (T--){ //cout<<T<<endl; int N;scanf("%d",&N); for(int i=1;i<=N;++i) scanf("%d",&D[i]); for(int i=1;i<=N;++i) for(int j=1;j<=N;++j){ F[i][i][j]=(j-1)*D[i]; F[i+1][i][j]=0; for(int k=i+1;k<=N;++k) F[i][k][j]=INF; } for(int len=1;len<=N;++len){ for(int pos=1;pos<=N-len+1;++pos) for(int i=1;i+len-1<=N;++i){ int last=i+len-1; for(int k=pos;k<=len+pos-1;++k){ int zj=i+k-pos; F[i][last][pos]=min(F[i][last][pos],F[i+1][zj][pos]+F[zj+1][last][k+1]+(k-1)*D[i]); } } } int ans=INF; for(int i=1;i<=N;++i) ans=min(ans,F[1][N][i]); printf("Case #%d: %d\n",++kase,ans); } return 0;}
Zoj 3469 Food Delivery
题意: 在x轴上一点X(坐标整数)有一家餐馆,x轴上有n个人,坐标不同(整数坐标)同 时定餐,餐厅仅一个送餐员,速度为1/V 米每秒,每个人等1min,都会增加对应
的不开心值,以后就不会定餐了,问不开心值sum最小值。
·这又是一道新颖的处理实际问题的应用题!
·这问题最先堵塞我的思维的是:我居然从餐厅点分析,就进行不下去了,无法切割成子问题!因为我无法从中部压缩区间为两段,这是不可能的;
·仔细分析(经过数月的经验积累):应该从两端分析,而恰恰最后一个送到的顾客存在于左右端点之中,我们可以假设最后一个送到的不为端点顾客,记为A,A位于餐厅与端点B之中,那么送给B时,难道不经过A吗?所以矛盾!
这是非常重要的结论!有了它,我们将问题归于首尾取石头问题,二种策略:
取首或尾,将问题规模减一!已经有区间DP的影子了!
·但是还有一个问题需要处理:我们的DP数组F[star][end][pos](pos=0,头取;pos=1,尾取)代表的含义,开始我想它代表[star,end]最先送完且最后送star或end(看pos)产生的不开心值sum,但通过举例和思维上的分析,这样存在问题:送完star在送star-1时,如果在[star,end]运送中,有可能会出现运送时间少,但sum大,而未被选中,但恰恰star-1的分钟不开心值很大,需要选择时间少的方案,那么出错了!
·处理这个问题,我们需要将思维在转一下,转移到从一个用户运送到另外一个用户的时间,恰好标志所有未收到货的人都此时等待这段时间!所以DP数组F[star][end][pos]
标志最先送完这些人对所有人造成的不开心之和,所以没有时间的包袱了!
·状态转移方程:
F[i][last][0]=min(F[i+1][last][0]+(sum-B[i+1][last])*(p[i+1].x-p[i].x),
F[i+1][last][1]+(sum-B[i+1][last])*(p[last].x-p[i].x));
·从第last个人的位置去到第i个人的位置需要跑p[i+1].x-p[i].x,这段
时间是除了第i+1到第last个外其他的人都必须等待的时间,所以选择
这种到i的方案需要在原F[i+1][last][0]上在增加(sum-B[i+1][last])*(p[i+1].x-p[i].x)
·剩下的分析一样
F[i][last][1]=min(F[i][last-1][0]+(sum-B[i][last-1])*(p[last].x-p[i].x),
F[i][last-1][1]+(sum-B[i][last-1])*(p[last].x-p[last-1].x));
·第i个人到last个人的单位不开心值的和B[i][last]可以在计算DP时,便带计算;因为它也是算出长度len-1的区间和,在基础上算len的区间和,并且此时因为缩减规模1,此时状态转移转移方程使用到的是长度为len的区间和;
·看题时:我非常不自信,硬是将1/V,结果wrong error爆了!一定要吸取教训!
/**这道题隐藏的很深: 实际上采取首尾分析的话,可以知道首尾中并有一个是当前全部中最后一个送到的; 首尾取石头;*740ms*/#include <iostream>#include <stdio.h>#include <string.h>#include <math.h>#include <algorithm>using namespace std;struct people{ int x; int b; bool operator <(const people &a){ return x<a.x; }}p[1010];long long F[1010][1010][2];//第三维为1:表示首为最后取//第三维为0:表示尾为最后取long long B[1010][1010];int main(){ int N,V,X; while(~scanf("%d%d%d",&N,&V,&X)){ for(int i=1;i<=N;++i) scanf("%d%d",&p[i].x,&p[i].b); sort(p+1,p+N+1); long long sum=0; for(int i=1;i<=N;++i){ B[i][i]=p[i].b; sum+=B[i][i]; } for(int i=1;i<=N;++i) F[i][i][0]=F[i][i][1]=abs(p[i].x-X)*sum; for(int len=2;len<=N;++len) for(int i=1;i+len-1<=N;++i){ int last=i+len-1; B[i][last]=B[i][last-1]+B[last][last]; F[i][last][0]=min(F[i+1][last][0]+(sum-B[i+1][last])*(p[i+1].x-p[i].x), F[i+1][last][1]+(sum-B[i+1][last])*(p[last].x-p[i].x)); F[i][last][1]=min(F[i][last-1][0]+(sum-B[i][last-1])*(p[last].x-p[i].x), F[i][last-1][1]+(sum-B[i][last-1])*(p[last].x-p[last-1].x)); long long ans=min(F[1][N][0],F[1][N][1]); cout<<ans*V<<endl; } return 0;}
Zoj 3537 Cake
题意:给你n个点,先看是否围成一个凸包,不能则输出: "I can't cut."
能,则将凸包切成三角形三点均为凸包顶点的三角形(互相无重叠部分,三角剖分);
除去凸包的边,新连边(点i,j相连)有一个cost(i,j)=|xi+xj|*|yi+yj|%p;求花费和最小值;
·这是一道综合题,需要判断是否为凸包,也利用凸包将区间DP进行包装!
·凸包点经过Javis March算法排成逆时针顺序后,因任意凸包的边都是切割后的一三角形的边,所以定下点1,n组成的边的切割三角形做最后得到的三角形!
类似poj1651线性删数问题,枚举区间除去端点i,last外,其它点看谁与端点组成三角形!记此点为k!
因为三角形不能区域覆盖,那么成功将区域分为三个[i,k]、[k,last]和三角形i|k|last;而即切了两下,cost[i][k]+cost[k][last](不用担心边ik为凸包的边,因为在划分时,将除边1n外所有凸包的边都做了花费计算,到最后在将凸包边的花费,减去就可以!)
·F[i][last]表示区间i到last组成区域切割成三角形的最小花费!
取除端点外的任意点k:
点i、k、last组成三角形,进行比较,得到最小花费值!
F[i][last]=min(F[i][last],F[i][k]+F[k][last]+cost[i][k]+cost[k][last]);
·编程中,用c语言输入时,输入case数量不知,scanf前没加~或没有判断是否不等于-1
或EOF,所以输入不能终止!LTE!
#include <iostream>#include <stdio.h>#include <math.h>#include <algorithm>#include <string.h>#define Size 310#define INF 1000000000using namespace std;int Left[Size];struct Point{ int x; int y; bool extreme; Point(int xx=0,int yy=0,bool e=false):x(xx),y(yy),extreme(e){} friend Point operator-(const Point &a,const Point &b) { return Point(a.x-b.x,a.y-b.y,false); } friend double operator^(const Point &a,const Point &b) { return (a.x*b.y-a.y*b.x); }}S[Size];int cost[Size][Size];int F[Size][Size];int LTL(int n){ int ltl=1; for(int i=2;i<=n;++i) { if(S[ltl].y>S[i].y||(S[ltl].y==S[i].y&&S[ltl].x>S[i].x))ltl=i; } return ltl;}int ToLeft(const Point &a,const Point &b,const Point &c){ if(((b-a)^(c-a))>0)return 1; if(((b-a)^(c-a))<0)return 0;//c在向量a->b右侧 return 2;//a,b,c共线}int tot;void Jarvis(int n,int <l){ ltl=LTL(n); int t=ltl;tot=0; do{ Left[++tot]=t; int s=0; for(int k=1;k<=n;++k) { if(k!=t&&k!=s&&( s==0||( ToLeft(S[t],S[s],S[k])==0 ) )) s=k; } t=s; }while(t!=ltl);}int main(){ int n,p; while(~scanf("%d%d",&n,&p)){ for(int i=1;i<=n;++i){ scanf("%d%d",&S[i].x,&S[i].y); } int ltl; Jarvis(n,ltl); if(tot!=n)printf("I can't cut.\n"); else { for(int i=1;i<n;++i){ F[i][i+1]=0; for(int j=i+1;j<=n;++j) cost[i][j]=(abs(S[Left[i]].x+S[Left[j]].x)*abs(S[Left[i]].y+S[Left[j]].y))%p; } for(int len=3;len<=n;++len) for(int i=1;i+len-1<=n;++i){ int last=i+len-1; F[i][last]=INF; for(int k=i+1;k<last;++k) F[i][last]=min(F[i][last],F[i][k]+F[k][last]+cost[i][k]+cost[k][last]); } for(int i=1;i<n;++i) F[1][n]-=cost[i][i+1]; printf("%d\n",F[1][n]); } } return 0;}
CodeForces 149D Coloring Brackets
题意:给你一个成功匹配的括号字符串(有‘(’、‘)’组成);现在给括号染色(只有两种颜色:红、蓝可染),每对匹配的括号中只能染一个,相邻括号不能染同色,
·写这道题,真心感觉它和其它区间DP不同,我花了很长一段时间,才真正意识上排除其它的入手方案,开始确定将F[t][i][j]的表达含义:
·i、j取0,1,2表示不取色、取红色和取蓝色
·i记录区间第first个括号染色i;
·j 记录区间第end个括号染色j;
·s[t]==‘(’,
·B[t]为此左括号匹配的右括号
·此最先表示[t,B[t]]之间括号染色后,前端为i色,后端为j色的方案数
·思路:
·我之所以认为与其它区间DP不同之处,就是这个len我们不好控制,用循环非常不好做,因为我们处理的是合法区间,根本无法处理非法区间,因为处理后我们无法处理其对应匹配的括号的染色情况;
·我们用递归来做,用合法区间段的组成定义来做,逻辑顺畅:
·如果处理合法区间已经为原子,不可在分,即区间长度为1,则
一端染一种色,另一端不染色的4种情况均为1,其它为0;
for(int i=1;i<3;++i){ F[s][i][0]=F[s][0][i]=1; }
例:()
·如果first对应的匹配括号刚好为end,即先求子问题[first+1,end-1]这个合法括号序列的所有情况。再求出[first,end]合法序列的所有情况;
注:因为首尾匹配,所以除了一端染色和一端不染色的F,其它均为0;
不妨以F[fisrt][i][0]为例,即可知它的染色情况数为全部F[first+1][j][k]且j≠i且k任意的和;
dp(s+1,e-1); for(int i=1;i<3;++i){ for(int j=0;j<3;++j) for(int k=0;k<3;++k){ if(j!=i)F[s][i][0]=(F[s][i][0]+F[s+1][j][k])%mod; if(k!=i)F[s][0][i]=(F[s][0][i]+F[s+1][j][k])%mod; } }
例:(()())
·如果first对应的匹配括号不为end,即存在多个合法序列并列;
也合法,即先处理dp(first,B[first]),在处理dp(B[first]+1,end);
顺利切割为两个子问题;类似上面过程,求出F[first][i][j];
注意求dp(first,B[first]),计算得到的F[first][i][j]表示区间
[first,B[first]]的情况数,下面我们要将F[first][i][j]迁移到表示区
间[first,end]的情况数;
·先用a[i][j]=0,来保留[first,end]的F[first][i][j]情况数,
这是个合并过!将F[first][i][u]与F[B[first]+1][v][j],(任意u、v)只需要保证合法,即要么两者同为0(不染色)要么两者不相等;
·最终统计所有F[1][i][j]情况即为答案!
·编程时注意:递归中合并时,我判u、v颜色相同,非法而不能合并;
但不有考虑到u==v==0,表示不染色,也是合法,能合并;
(longlong)(F[s][i][u]*F[B[s]+1][v][j])%mod;
无效,F[s][i][u]与F[B[s]+1][v][j]乘积仍是int,所以等不到强制类型转换,我们应该最先将两者类型转换!
dp(s,B[s]); dp(B[s]+1,e); int a[3][3]; for(int i=0;i<3;++i) for(int j=0;j<3;++j){ a[i][j]=0; for(int u=0;u<3;++u) for(int v=0;v<3;++v) { if(u!=v||(u==0&&v==0))a[i][j]+=(long long)(((long long)F[s][i][u])*((long long)F[B[s]+1][v][j]))%mod; a[i][j]%=mod; } } for(int i=0;i<3;++i) for(int j=0;j<3;++j) F[s][i][j]=a[i][j];
- 区间DP
- 区间DP
- 区间DP
- 区间DP
- ##区间dp##
- 区间dp
- 区间DP
- 区间dp
- 区间dp
- 区间dp
- 区间dp
- 区间dp
- 区间dp
- 区间dp
- 区间DP
- 区间DP
- 区间DP
- 区间dp
- 有向图的DFS和BFS(邻接矩阵实现)
- android stuido的aar文件怎么使用
- 名片管理系统 以下代码个人原创,在需要说明的地方我在后面加了注释,当然我的代码是希望给大家一个思想,还有很多很多不足之处,希望大佬们发现后私信我哦,咱们一起探讨(嘻嘻,说得有点小装逼呀,其实是老师发
- 欧几里得几何(3)Gamma
- 搭建Linux的Ubuntu环境
- 区间DP
- 机器学习-python编写主成分分析(PCA)数据降维
- fiddler手机抓包 iOS iPhone 设置方法
- java--水仙花数
- sql语句
- 关于Promise.then()的思考
- 2.jquery选择器
- (实用)导出word里面生成excel
- easyui combobox 制作带有复选框的下拉菜单