kuangbin专题十二 基础DP

  • A - HDU1024 Max Sum Plus Plus
  • B - HDU1029 Ignatius and the Princess IV
  • C - HDU1069 Monkey and Banana
  • D - HDU1074 Doing Homework
  • E - Hdu1087 Super Jumping! Jumping! Jumping!
  • F - Hdu1114 Piggy-Bank
  • G - HDU1176 免费馅饼
  • H - HDU1260 Tickets
  • I - Hdu1257 最少拦截系统
  • J - HDU1160 FatMouse's Speed
  • K - [POJ1015](http://poj.org/problem?id=1015) Jury Compromise
  • L - [POJ1458](http://poj.org/problem?id=1458) Common Subsequence
  • M - [POJ1661](http://poj.org/problem?id=1661) Help Jimmy
  • N - [POJ2533](http://poj.org/problem?id=2533) Longest Ordered Subsequence
  • O - [POJ3186](http://poj.org/problem?id=3186) Treats for the Cows
  • P - HDU1078 FatMouse and Cheese
  • Q - HDU285 Phalanx
  • R - [POJ3616](http://poj.org/problem?id=3616) Milking Time
  • S - [POJ3666](http://poj.org/problem?id=3666) Making the Grade

A - HDU1024 Max Sum Plus Plus

一维code:

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
using namespace std;
const int N=1e6+10,inf=0x3f3f3f3f;
int pre[N],f[N],a[N],m,n;int main()
{while(~scanf("%d %d",&m,&n)){for(int i=1;i<=n;i++) scanf("%d",&a[i]);memset(pre,0,sizeof(pre));memset(f,0,sizeof(f));int Max;for(int i=1;i<=m;i++){Max=-inf;for(int j=i;j<=n;j++){f[j]=max(f[j-1],pre[j-1])+a[j];pre[j-1]=Max;Max=max(Max,f[j]);}}printf("%d\n",Max);}return 0;
}

二维code(慎用):

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
using namespace std;
const int N=1e6+10,inf=0x3f3f3f3f;
int f[100][N],a[N],m,n;int main()
{while(~scanf("%d %d",&m,&n)){for(int i=1;i<=n;i++) scanf("%d",&a[i]);memset(f,0,sizeof(f));int Max;for(int i=1;i<=m;i++){Max=-inf;for(int j=i;j<=n;j++){f[i][j]=f[i-1][j-1];f[i][j]=max(f[i][j],f[i][j-1])+a[j];f[i][j-1]=Max;               Max=max(Max,f[i][j]);          }}printf("%d\n",Max);}return 0;
}

题意:给你n个数,让你求m个子段和的最大值,子段之间不能相重合。

思路:这道题一开始想的时候我们可以定义f[i][j]:前j个数中当前i个子段和的最大值,那么状态转移方程为:f[i][j]=max(f[i-1][j-1],f[i][j-1])+a[j],但其实这样做空间可能会超限(因为现在HDU进不去,我不好判断),网上找的题解用的都是一维的,所以我就降维。仔细想想,上一层的答案我们可以用一个数组pre记录下来,这样我们就可以做到用一维来解决了。f[j]:当前层数的前i个数的子段和的最大值,那么状态转移方程为f[j]=max(f[j-1],pre[j-1])+a[j]。另外有一点要注意一下:pre[j-1]=Max这个式子一定要放在上面两个式子之间,因为你要先取完当前下标的最大值Max后再更新当前下标pre的值,不然肯定会出错的!

B - HDU1029 Ignatius and the Princess IV

code:

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
const int N=1e6+10;
int n,a[N];
int main()
{while(cin>>n){memset(a,0,sizeof(a));int flag=0;for(int i=1;i<=n;i++){int c;cin>>c;a[c]++;if(a[c]>=((n+1)/2)&&flag==0) {cout<<c<<"\n";flag=1;continue;}}}return 0;
}

水题,不过多解释。

C - HDU1069 Monkey and Banana

code:

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N=200;
struct Node{int x,y,z;
}block[200];
int t,n,f[N];
bool cmp(Node a,Node b)
{if(a.x!=b.x) return a.x<b.x;else return a.y<b.y;
}
int main()
{t=1;while(cin>>n){if(n==0) break;for(int i=0;i<n;i++){int a,b,c;cin>>a>>b>>c;block[6*i].x=a,block[6*i].y=b,block[6*i].z=c;block[6*i+1].x=a,block[6*i+1].y=c,block[6*i+1].z=b;block[6*i+2].x=b,block[6*i+2].y=a,block[6*i+2].z=c;block[6*i+3].x=b,block[6*i+3].y=c,block[6*i+3].z=a;block[6*i+4].x=c,block[6*i+4].y=a,block[6*i+4].z=b;block[6*i+5].x=c,block[6*i+5].y=b,block[6*i+5].z=a;}sort(block,block+6*n,cmp);for(int i=0;i<6*n;i++){f[i]=block[i].z;for(int j=0;j<i;j++){if(block[i].x>block[j].x&&block[i].y>block[j].y)f[i]=max(f[i],f[j]+block[i].z);    }}int maxn=-1;for(int i=0;i<6*n;i++){if(maxn<f[i])maxn=f[i];}printf("Case %d: maximum height = %d\n",t++,maxn);}return 0;
}

题意:有n种类型的矩形方块,每个方块可用无限多个,问最多能叠多高,叠加的条件是上面的方块的长和宽要严格小于下面方块的长和宽。

思路:这道题有点像LIS问题,我们可以把长宽高任意组合,一种类型有6种组合,那么n种类型有6n种组合,之后把这6n种组合存在结构体数组内,按照长度相同时宽度从小到大,长度不相同时长度从小到大排序。状态转移方程为:f[i]=max(f[i],f[j]+block[i].z),不过要加一个判断条件:block[i].x>block[j].x&&block[i].y>block[j].y,然后在6*n种答案里选最大值即可。

D - HDU1074 Doing Homework

code:

#include<iostream>
#include<cstdio>
using namespace std;
const int N=20,inf=0x3f3f3f3f;
struct Node{string name;int deadline;int day;
}course[N];
struct Dp{int now;存放当前的时间int pre;//存放前一个状态到达当前状态所完成的课程int score;//存放当前减少的分数
}dp[1 << N];
void output(int x)
{int l=0;int ans[N];while(x){ans[l++]=dp[x].pre;x=x-(1 << dp[x].pre);}for(int i=l-1;i>=0;i--)cout<<course[ans[i]].name<<"\n";
}
int main()
{int t,n;scanf("%d",&t);while(t--){scanf("%d",&n);for(int i=0;i<n;i++){cin >> course[i].name >> course[i].deadline >> course[i].day;}dp[0].now=0;dp[0].pre=-1;dp[0].score=0;for(int i=1;i<(1 << n);i++)dp[i].score = inf;for(int i=0;i<(1 << n);i++)//枚举所有状态{for(int j=0;j<=n-1;j++){int tmp=(1 << j);if(i & tmp) continue;//如果第j门课程已经做了int t=i | tmp;//加入第j门课程,到达当前状态int time=dp[i].now+course[j].day;//计算当前时间int sc=0;if(time>course[j].deadline)//如果时间超了就计算要被减去的分数sc=time-course[j].deadline;sc+=dp[i].score;//跟上一个状态的分数相加if(dp[t].score>sc)//看是否能更新{dp[t].now=time;dp[t].pre=j;dp[t].score=sc;}}}cout<<dp[(1 << n)-1].score<<"\n";//找到所有功课都完成的状态的结果output((1 << n)-1);}return 0;
}

题意:给你n门课程的名字、截止时间和做完这门课程需要花的时间,课程没有在规定时间内做完会扣分,求一种方案使得扣分最少,输出最少扣分和方案。

思路:参考了这位博主的代码再加了些自己的看法。这道题我们可以先看数据范围,1<=N<=15,这就提醒我们可以用状态压缩DP来写。1代表写了,0代表没写,用二进制表示课程完成情况,我们可以规定1011代表第1、2、4三门课程完成了,第3门课程没完成,也可以规定1011代表第1,3,4三门课程完成了,第2门课程没完成,但在这里你只能选前者因为题目中规定了字母序最小的,如果你要按后面的来写,我认为是写不出来的,答案一定会错!还有一点要注意,博主的代码循环跟我的代码循环刚好相反:for(int j=n-1;j>=0;j–),在这里循环的顺序不会影响最终结果,这是我思考了很长时间得出的结论,影响最终结果的是你对1011的规定是前者还是后者

E - Hdu1087 Super Jumping! Jumping! Jumping!

code:

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N=1005;
ll ans,dp[N],a[N],n;
int main()
{while(cin>>n){if(n==0) break;for(int i=1;i<=n;i++)scanf("%d",&a[i]);ans=-1;for(int i=1;i<=n;i++){dp[i]=a[i];for(int j=1;j<=i;j++){if(a[i]>a[j]){dp[i]=max(dp[j]+a[i],dp[i]);}}ans=max(ans,dp[i]);}cout<<ans<<"\n";}return 0;
}

题意:就是找一个上升子序列,使他的和最大。

思路:可以类比求最长上升子序列的题来写。

F - Hdu1114 Piggy-Bank

code:

#include<iostream>
#include<algorithm>
#include<cstdio>
using namespace std;
int t;
int e,f,n,v[505],w[505],dp[10005];
int main()
{scanf("%d",&t);while(t--){cin>>e>>f>>n;for(int i=1;i<=n;i++)scanf("%d %d",&v[i],&w[i]);dp[0]=0;for(int i=1;i<=f-e;i++) dp[i]=0x3f3f3f3f;for(int i=1;i<=n;i++)for(int j=w[i];j<=f-e;j++){dp[j]=min(dp[j],dp[j-w[i]]+v[i]);}if(dp[f-e]!=0x3f3f3f3f)cout<<"The minimum amount of money in the piggy-bank is "<<dp[f-e]<<"."<<endl;else cout<<"This is impossible."<<endl;}return 0;
}

题意:给定一个重量为f-e的背包和n种类型的物品,每个物品有重量和价值,每种物品有无限多个,问背包恰好放满且背包里物品总价值最低为多少。

思路:基本跟完全背包问题相同,就是要把max改为min,其他代码相同。

G - HDU1176 免费馅饼

1.code:

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int n,dp[100005][20];
int main()
{while(~scanf("%d",&n)){if(n==0) break;int maxt=-1;memset(dp,0,sizeof(dp));for(int i=0;i<n;i++){int t,p;scanf("%d%d",&p,&t);dp[t][p]++;maxt=max(maxt,t);}for(int i=maxt;i>=0;i--){for(int j=0;j<11;j++){dp[i][j]+=max(dp[i+1][j],max(dp[i+1][j-1],dp[i+1][j+1]));}}printf("%d\n",dp[0][5]);}return 0;
}

2.code:

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int n,dp[100005][20];
int main()
{while(~scanf("%d",&n)){if(n==0) break;int maxt=-1;memset(dp,0,sizeof(dp));for(int i=0;i<n;i++){int t,p;scanf("%d%d",&p,&t);dp[t][p]++;maxt=max(maxt,t);}for(int i=1;i<=maxt;i++){for(int j=0;j<11;j++){if(j>=1)dp[i][j]+=max(dp[i-1][j],max(dp[i-1][j-1],dp[i-1][j+1]));else dp[i][j]+=max(dp[i-1][j],dp[i-1][j+1]);}}int ans=-1;for(int j=0;j<11;j++)ans=max(ans,dp[maxt][j]);printf("%d\n",ans);}return 0;
}

题意:一个人在0~10这11个位置捡馅饼,一开始他在5这个位置,第1s时他能捡4,5,6这3个位置的馅饼中的一种。馅饼会在T时间掉落在x这个位置,给你n个馅饼的x和T,问最多能捡几个馅饼。馅饼可能会在同一时间同一位置掉落多个馅饼

思路:定义dp[i][j]:截止到第j秒i位置捡到的馅饼数量的最大值,我们有两种思路,你可以从前往后算,也可以从后往前算(时间上),如果是从后往前算,就是第一种代码,答案就是dp[0][5]了,反之就是第二种代码,要在最大时间的0~10这11个位置取最大值。

H - HDU1260 Tickets

code:

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int N=2005;
int s[N],d[N],dp[N];
int main()
{int n;cin>>n;while(n--){int k;cin>>k;memset(dp,0,sizeof(dp));for(int i=1;i<=k;i++)cin>>s[i];for(int i=2;i<=k;i++)cin>>d[i];d[1]=s[1];for(int i=2;i<=k;i++){dp[i]=min(dp[i-1]+s[i],dp[i-2]+d[i]);}int ss=d[k]%60;int m=d[k]/60%60;int h=d[k]/3600;h+=8;if(h<=12)printf("%02d:%02d:%02d am\n",h,m,ss);else printf("%02d:%02d:%02d pm\n",h-12,m,ss);}return 0;
}

题意:给你N个测试样例,每个样例会给出k个人买票所需的时间以及k-1个两个相邻的人买票总共需要的时间,问这k个人买票所需的最少时间。

思路:线性DP,dp[i]表示第1个人到第i个人买票花的最少时间,那么状态转移方程为:dp[i]=min(dp[i-1]+s[i],dp[i-2]+d[i]),dp[k]就是答案,然后根据样例输出即可。

I - Hdu1257 最少拦截系统

code:

#include<iostream>
#include<algorithm>
using namespace std;
const int N=1e5+5;
int a[N],dp[N];
int main()
{int n;while(cin>>n){for(int i=0;i<n;i++) {cin>>a[i];dp[i]=1;}for(int i=0;i<n;i++){for(int j=0;j<i;j++){if(a[j]<a[i]){dp[i]=max(dp[i],dp[j]+1); }}}int ans=0;for(int i=0;i<n;i++)ans=max(ans,dp[i]);cout<<ans<<"\n";}return 0;
}

题意:不解释,看题目,很直白。

思路:这道题一开始我以为是求多次最长下降子序列,每次求完就删掉,直到全部删完,输出次数即可,但后来发现这样做不太现实,做不出来。想过一个思路,发现如果后面导弹的高度比前面的高,就要换过一个导弹系统,于是我们就可以按照最长上升子序列的代码来写即可得出答案。

J - HDU1160 FatMouse’s Speed

code:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
struct Node{int w,s,id;
}m[1005];
struct DP{int num,pre;
}dp[1005];
bool cmp(Node a,Node b)
{if(a.w!=b.w) return a.w<b.w;
}
void output_path(int x)
{if(dp[x].pre!=-1)output_path(dp[x].pre);printf("%d\n",m[x].id);
}
int main()
{int t=1;while(~scanf("%d %d",&m[t].w,&m[t].s)){m[t].id=t;++t;}sort(m+1,m+1+t,cmp);int ans=0,x=-1;for(int i=1;i<=t;i++)dp[i].num=1,dp[i].pre=-1;for(int i=1;i<=t;i++){for(int j=1;j<i;j++){if((m[j].w<m[i].w)&&(m[j].s>m[i].s)){if(dp[i].num<dp[j].num+1){dp[i].num=dp[j].num+1;dp[i].pre=j;}             }}if(ans<dp[i].num)ans=dp[i].num,x=i;}printf("%d\n",ans);output_path(x);return 0;
}

题意:给你一定数量的老鼠,每个老鼠有体重和速度两个属性,需要求一个老鼠序列,使得这个序列的长度最长,输出序列的长度和路径。序列要符合老鼠的体重越大,速度越小的结论。

思路:这道题一开始不知道怎么输出路径,看了题解之后才恍然大悟。就是要在dp这个结构体数组中加一个pre这个变量来记录路径,还要记录序列最长的最后一个元素的下标就可以解决了。我的做法是用一个结构体数组m来记录体重,速度和下标,然后对这个数组按照体重从小到大排序,然后在循环中进行比较,更新答案,输出路径用一个递归函数来解决。

K - POJ1015 Jury Compromise

code:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<vector>
#include<cstring>
using namespace std;
int dp[21][805];
//现用dp(j, k)表示,取j 个候选人,使其辩控差为k 的所有方案中,
//辩控和最大的那个方案(该方案称为“方案f(j, k)”)的辩控和
vector<int> path[21][805];//记录路径  path[i][j] 取i个人  他们的差为j时候的路径
int main()
{int n,m,t=1;while(~scanf("%d %d",&n,&m)){if(n==0&&m==0) break;int sub[210],_plus[210];for(int i=0;i<n;i++){int d,p;scanf("%d %d",&d,&p);sub[i]=d-p;_plus[i]=d+p;}for(int i=0;i<m;i++){for(int j=0;j<805;j++){path[i][j].clear();}}memset(dp,-1,sizeof(dp));int fix=20*m;dp[0][fix]=0;for(int k=0;k<n;k++){for(int i=m-1;i>=0;i--){for(int j=0;j<2*fix;j++){if(dp[i][j]>=0){if((dp[i+1][j+sub[k]])<=(dp[i][j]+_plus[k])){dp[i+1][j+sub[k]]=dp[i][j]+_plus[k];path[i+1][j+sub[k]]=path[i][j];path[i+1][j+sub[k]].push_back(k);}}}}}int i;for(i=0;dp[m][fix+i]==-1&&dp[m][fix-i]==-1;i++);int temp = (dp[m][fix+i] > dp[m][fix-i]) ? i : -i;int sumD=(dp[m][fix+temp]+temp)/2;int sumP=(dp[m][fix+temp]-temp)/2;printf("Jury #%d\n",t++);printf("Best jury has value %d for prosecution and value %d for defence:\n",sumD,sumP);for(int i=0;i<m;i++)printf(" %d",path[m][fix+temp][i]+1);printf("\n\n");}return 0;
}

题意:从n个人中选出m个人,选法为控方满意度之和s1与辩方满意度之和s2的差的绝对值最小,若有多种方案,则选择控方满意度之和s1与辩方满意度之和s2的和最大的一组,先输出这是第几组数据,下一行分别输出选择出来的方案中的控方满意度之和s1与辩方满意度之和s2。最后一行输出选择的人的编号,按从小到大的顺序排列。

思路:这道题不仅本身就很难,题目还贼难理解,英文的楞是看了4,5遍没看懂题意,翻译了一下才看懂了。一开始没啥思路,参考了一些大佬的思路。首先定义dp[j][k]:取j 个候选人,使其辩控差为k 的所有方案中,辩控和最大的那个方案的辩控和,那么状态转移方程为:dp[i+1][j+sub[k]]=dp[i][j]+_plus[k]。具体思路见代码。
有几点需要注意
1.不能直接记录差的绝对值!因为在后面循环遍历中这样做会覆盖很多答案,导致出错。
2.在写的时候是以dp[0][fix]作为起始点的,这是为了防止差是负数的情况

L - POJ1458 Common Subsequence

code:

#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
using namespace std;
const int N=1e3+10;
int dp[N][N];
int main()
{char a[N],b[N];while(~scanf("%s %s",a,b)){int n=strlen(a);int m=strlen(b);memset(dp,0,sizeof(dp));for(int i=0;i<n;i++){for(int j=0;j<m;j++){if(a[i]==b[j])dp[i+1][j+1]=dp[i][j]+1;else dp[i+1][j+1]=max(dp[i+1][j],dp[i][j+1]);}}printf("%d\n",dp[n][m]);}return 0;
}

题意:给定两个序列,求最长公共子序列的长度。

思路:经典LCS模板题。

M - POJ1661 Help Jimmy

code:

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
using namespace std;
const int N=1005,INF=0x3f3f3f3f;
int dp[N][5];
int n,x,y,Max;
struct Plat{int x1,x2,high;
}plat[N];
bool cmp(Plat a,Plat b)
{return a.high<b.high;
}
void LeftMove(int i)
{int k=i-1;while((k>0)&&(plat[i].high-plat[k].high<=Max)){if((plat[i].x1>=plat[k].x1)&&(plat[i].x1<=plat[k].x2)){dp[i][0]=plat[i].high-plat[k].high+min(plat[i].x1-plat[k].x1+dp[k][0],plat[k].x2-plat[i].x1+dp[k][1]);return;}else --k;}if(plat[i].high-plat[k].high>Max)dp[i][0]=INF;else dp[i][0]=plat[i].high;
}
void RightMove(int i)
{int k=i-1;while((k>0)&&(plat[i].high-plat[k].high<=Max)){if((plat[i].x2>=plat[k].x1)&&(plat[i].x2<=plat[k].x2)){dp[i][1]=plat[i].high-plat[k].high+min(plat[i].x2-plat[k].x1+dp[k][0],plat[k].x2-plat[i].x2+dp[k][1]);return;}else --k;}if(plat[i].high-plat[k].high>Max)dp[i][1]=INF;else dp[i][1]=plat[i].high;
}
int ShortestTime()
{for(int i=1;i<=n+1;i++){LeftMove(i);RightMove(i);}return min(dp[n+1][0],dp[n+1][1]);
}
int main()
{int t;scanf("%d",&t);while(t--){scanf("%d%d%d%d",&n,&x,&y,&Max);memset(dp,0,sizeof(dp));for(int i=1;i<=n;i++){scanf("%d%d%d",&plat[i].x1,&plat[i].x2,&plat[i].high);}plat[n+1].x1=x;plat[n+1].x2=x;plat[n+1].high=y;plat[0].x1=-20000;plat[0].x2=20000;plat[0].high=0;sort(plat,plat+n+2,cmp);printf("%d\n",ShortestTime());}return 0;
}

题意:题目简单直白,见题目。

思路:线性DP,定义dp[i][j]:j=0时代表向左走,j=1时代表向右走,把老鼠也看成一个平台,从第i个平台向左走或向右走到达地面的最短时间。那么答案就是max(dp[n+1][0],dp[n+1][1])。

N - POJ2533 Longest Ordered Subsequence

code:

#include<iostream>
#include<algorithm>
using namespace std;
int dp[1005],a[1005];
int main()
{int n,ans=-1;scanf("%d",&n);for(int i=1;i<=n;i++){scanf("%d",&a[i]);}for(int i=1;i<=n;i++){dp[i]=1;for(int j=1;j<i;j++){if(a[i]>a[j]){dp[i]=max(dp[i],dp[j]+1);}}ans=max(ans,dp[i]);}printf("%d",ans);return 0;
}

题意:给出n个数,求最长上升子序列的数量。

思路:经典LIS模板题。

O - POJ3186 Treats for the Cows

code:

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N=2005;
int a[N],dp[N][N];int main()
{int n;scanf("%d",&n);for(int i=1;i<=n;i++)scanf("%d",&a[i]);for(int len=1;len<=n;len++){for(int i=1,j=i+len-1;i<=n;i++,j++){dp[i][j]=max(dp[i+1][j]+a[i]*(n-len+1),dp[i][j-1]+a[j]*(n-len+1));}}printf("%d",dp[1][n]);return 0;
}

题意:双端队列里取数,每次取数都乘上它取出时的序列号,问和最大为多少。

思路:dp[i][j]表示序列从i~j的所求值。经典区间DP,不过是倒着推,难点在于当前区间天数的表示。

P - HDU1078 FatMouse and Cheese

code:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
int a[105][105];
bool vis[105][105];
int ans[105][105],dx[]={1,-1,0,0},dy[]={0,0,1,-1};
int n,k;
void dfs(int x,int y)
{for(int i=0;i<4;i++){for(int j=1;j<=k;j++){int xx=x+dx[i]*j;int yy=y+dy[i]*j;if(a[x][y]<a[xx][yy]&&vis[xx][yy]==0&&xx>=0&&xx<n&&yy>=0&&yy<n){vis[xx][yy]=1;ans[xx][yy]=max(ans[xx][yy],ans[x][y]+a[xx][yy]);dfs(xx,yy);vis[xx][yy]=0;}}}
}
int main()
{while(~scanf("%d%d",&n,&k)){if(n==-1&&k==-1) return 0;memset(vis,0,sizeof(vis));for(int i=0;i<n;i++)for(int j=0;j<n;j++){scanf("%d",&a[i][j]);ans[i][j]=a[i][j];}dfs(0,0);int anss=-1;for(int i=0;i<n;i++)for(int j=0;j<n;j++){anss=max(anss,ans[i][j]);}printf("%d\n",anss);}return 0;
}

题意:老鼠一开始在(0,0)的位置,每个位置都有一定数量的食物,老鼠一次最多走k步,规定走过的位置的食物量要越来越大,问老鼠最多能吃的食物量。

思路:挺简单的,记忆化搜索DP。

Q - HDU285 Phalanx

code:

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int maxn=1005;
char st[maxn][maxn];
int dp[maxn][maxn];
int main()
{int n,ans,t1,t2;while(scanf("%d",&n) && n){ans=0;for(int i=0;i<n;i++)scanf("%s",st[i]);for(int i=0;i<n;i++){for(int j=0;j<n;j++){if(i==0||j==n-1)dp[i][j]=1;else{t1=i,t2=j;while(t1>=0&&t2<n){if(st[t1][j]==st[i][t2])t1--,t2++;else break;}int t=i-t1;dp[i][j] = min(i-t1, dp[i-1][j+1]+1);}ans=max(ans,dp[i][j]);}}printf("%d\n",ans);}return 0;
}

题意:给定一个n行n列的字符矩阵。求这个矩阵的最大对称子矩阵的大小。在这里的对称指的是关于左下和右上相连的对角线对称。

思路:我们定义dp[i][j]:0~i, j~n-1形成的矩阵的最大对称子矩阵的大小。

我们可以先将dp[i][j]全部初始化为1,也可以像上面代码一样只初始化第一行和第n-1列为1,后面的具体操作可结合代码和图片来理解。

R - POJ3616 Milking Time

code:

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
struct Node{int l,r,e;
}a[1005];
int f[1005];
bool cmp(Node a,Node b)
{return a.r<b.r;
}
int main()
{int N,M,R;scanf("%d%d%d",&N,&M,&R);for(int i=0;i<M;i++){scanf("%d%d%d",&a[i].l,&a[i].r,&a[i].e);a[i].r+=R;//预处理}sort(a,a+M,cmp);//按右端点从小到大排序int k;k=0;for(int i=1;i<=N+R;i++){f[i]=f[i-1];while(a[k].r==i) f[i]=max(f[i],f[a[k].l]+a[k++].e);//可能有多个值相等,要用while}printf("%d",f[N+R]);return 0;
}

题意:给了M个时间段和产奶量,要在规定的N个小时内分配合理时间使总产奶量最大。注意奶牛产完奶后要有R个小时休息后才能继续工作。

思路:见代码和注释。

S - POJ3666 Making the Grade

code:

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N=2005,INF=0x3f3f3f3f;
int dp[N][N],a[N],b[N],n;
void solve()
{for(int i=1;i<=n;i++){int minn=INF;for(int j=1;j<=n;j++){int cost=abs(a[i]-b[j]);minn=min(minn,dp[i-1][j]);//dp[i-1][j]指的是前i-1个数最大值为b[j]时的最小代价dp[i][j]=cost+minn;}}int ans=INF;for(int i=1;i<=n;i++)ans=min(ans,dp[n][i]);printf("%d",ans);
}
int main()
{scanf("%d",&n);for(int i=1;i<=n;i++){scanf("%d",&a[i]);b[i]=a[i];}sort(b+1,b+1+n);solve();return 0;
}

这道题一开始我没啥思路,后来看了题解才知道是线性DP+离散化。可以参考这位博主的思路。

题意:给出一个长度为 n 的序列,要求使序列变为非递增或非递减序列,问花费的最少代价。在之后的做题中统一将其改为非递减序列。原因后面会解释。

思路:题目显然是 LIS 问题的变种,线性 DP 无疑

对于长度为 i 的序列,其最优解只与两个值有关,一个是这个序列处理的代价,另一个则是这个序列处理后的尾部的值。那么显然,我们希望序列处理的代价越小越好,而序列处理后的尾部的值越小越好,因为当序列处理完毕后,尾部的值是这个序列的最大值,其值越小,第 i+1 个数不花任何代价直接接在其后面的可能性就更大。

设 dp[i][j] 为长度为前 i 个数构成的序列,且处理完最大值为 j 所花费的相应代价,那么可以得出状态转移方程:

dp[i][j]=abs(j-a[i])+min(dp[i-1][k]),k<=j,其中 abs(j-a[i]) 代表处理的代价,min(dp[i-1][k]) 代表前 i-1 个数构成的序列最大值为 k 时所花费的最小代价

注意到 j 最大可达到 1,000,000,000,那么显然枚举的话一定会 TLE,而 n 的大小最大只有 2000,那么使用离散化的思想,先对序列 a[i] 进行处理,即:

对于长度为 n 的序列,可以发现序列中的某个数 a[i],无论怎么变化,最小代价一定是序列中的某个数与当前这个数 a[i] 的差,因此我们可以建立一个备份数组 b[i],将原来的 j-a[i] 转变为 b[j]-a[i],也即 abs(b[j]-a[i]),然后再进行动态规划

最后,再从 dp[n][1~n] 中寻找最小值即可。

有几点我的看法:

1.我感觉博主原代码中的注释有点问题,所以代码我改了一下,dp[i][j]在离散化之后的意义就变了,变为前i个数最大值为b[j]时的最小代价
2.解释下为什么统一将序列改为非递减,我认为在原文中他有两个方向可以走,你从左边开始走序列为非递减,那么你从右边开始走序列就为非递增,所以我们可以统一将序列改为非递减。
3.为什么对于长度为 n 的序列,可以发现序列中的某个数 a[i],无论怎么变化,最小代价一定是序列中的某个数与当前这个数 a[i] 的差?这点很好解释,当你要改变当前的值时,你所替换的值要么是与前一个改变后的值相等,要么与后一个未改变的值相等,毕竟是要使尾部的值越小越好,这样改动才能使和最小。

kuangbin专题十二 基础DP相关推荐

  1. [kuangbin带你飞]专题十二 基础DP1 题解+总结

    kuangbin带你飞:点击进入新世界 总结: 简单dp,最近在做,持续更新. 文章目录 总结: 1.Max Sum Plus Plus 2.Ignatius and the Princess IV ...

  2. [kuangbin带你飞]专题十二 基础DP1

    A - Max Sum Plus Plus (HDU 1024) 题意:将n个数取m段且不相交,求m段数字和最大值: dp[i][j]:前i个数字分成j段的最大值. 边界dp[0][0] = 0; d ...

  3. kuangbin 专题十二: 基础DP1 Tickets

    题目链接: 传送门 #include<cstdio> #include<cstring> #include<algorithm> using namespace s ...

  4. [kuangbin带你飞]专题十二 基础DP1 C - Monkey and Banana HDU - 1069

    C - Monkey and Banana HDU - 1069 题目链接:https://vjudge.net/contest/68966#problem/C 题目: A group of rese ...

  5. [C#基础知识系列]专题十二:迭代器

    引言: 在C# 1.0中我们经常使用foreach来遍历一个集合中的元素,然而一个类型要能够使用foreach关键字来对其进行遍历必须实现IEnumerable或IEnumerable<T> ...

  6. [C# 网络编程系列]专题十二:实现一个简单的FTP服务器

    引言: 休息一个国庆节后好久没有更新文章了,主要是刚开始休息完心态还没有调整过来的, 现在差不多进入状态了, 所以继续和大家分享下网络编程的知识,在本专题中将和大家分享如何自己实现一个简单的FTP服务 ...

  7. 专题十二:实现一个简单的FTP服务器

    引言: 在本专题中将和大家分享如何自己实现一个简单的FTP服务器.在我们平时的上网过程中,一般都是使用FTP的客户端来对商家提供的服务器进行访问(上传.下载文件),例如我们经常用到微软的SkyDriv ...

  8. kuangbin专题十六 KMP扩展KMP HDU3068 最长回文

    给出一个只由小写英文字符a,b,c...y,z组成的字符串S,求S中最长回文串的长度. 回文就是正反读都是一样的字符串,如aba, abba等 Input输入有多组case,不超过120组,每组输入为 ...

  9. kuangbin专题十六 KMP扩展KMP HDU2594 Simpsons’ Hidden Talents

    Homer: Marge, I just figured out a way to discover some of the talents we weren't aware we had. Marg ...

最新文章

  1. python获取url参数 类继承_python之类的继承
  2. python列表片段_Python列表片段索引操作,python
  3. 创建多线程_你真的了解多线程吗?
  4. github详细搜索
  5. Flask+uwsgi+Nginx环境搭建
  6. 分步表单_表单设计-掌握表单设计方法(表单体验篇)
  7. java jframe 设置背景图片_JFrame如何设置背景图片
  8. 基于matlab的gps信号仿真123,MATLABGPS信号仿真完整源代码.doc
  9. 使用Xshell连接Linux虚拟机(NAT)
  10. Windows部署Tomcat8启动服务
  11. 离散数学及其应用 第一章习题
  12. Intel ICH9 sata驱动
  13. 数据库课程设计——某商店进销存管理系统(附Java源码与课程设计报告)
  14. idea前端可视化_jsp可视化开发工具_netbeans jsp可视化_idea 可视化开发 jsp
  15. 山东农业大学计算机考研资料汇总
  16. bmp怎样转成jpg?
  17. 使用node实现简单的增删改查功能的小demo
  18. CryEngine技术讲解
  19. php二级分销kohana源码,php框架kohana(二)
  20. vs+cmake完美编译RTS游戏,类似魔兽争霸源码

热门文章

  1. atheros有线网卡LINUX驱动,新版Atheros AR81系列有线驱动
  2. python在线投票系统讲解_Python开发基础-项目实训-在线投票系统ppt课件
  3. 在VMware8.0下安装crux2.6
  4. 牛腩新闻发布--过程或函数 'news_selectByCaId' 需要参数 '@caid',但未提供该参数(一)
  5. 从键盘输入10个正负相间的整数,输出个位数是奇数、十位数是偶数的所有数。
  6. 《Hadoop权威指南》学习笔记(一)
  7. Flink跟着问题读源码 - SlidingEventTimeWindows接reduce结果数据倍增
  8. 公交换乘GIS地图应用解决方案
  9. IE6、IE7、IE8、IE9兼容性问题解决办法
  10. 五色石FCS周报 2019.5.13-2019.5.19