区间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 1000000000
using 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 1000000000
using 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];