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] 的差?这点很好解释,当你要改变当前的值时,你所替换的值要么是与前一个改变后的值相等,要么与后一个未改变的值相等,毕竟是要使尾部的值越小越好,这样改动才能使和最小。