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

时间:2022-11-22 03:02:19
 
 
   
ID
Origin
Title
  167 / 465 Problem A HDU 1024 Max Sum Plus Plus
  234 / 372 Problem B HDU 1029 Ignatius and the Princess IV
  161 / 259 Problem C HDU 1069 Monkey and Banana
  104 / 188 Problem D HDU 1074 Doing Homework
  153 / 248 Problem E HDU 1087 Super Jumping! Jumping! Jumping!
  127 / 215 Problem F HDU 1114 Piggy-Bank
  151 / 428 Problem G HDU 1176 免费馅饼
  118 / 199 Problem H HDU 1260 Tickets
  167 / 292 Problem I HDU 1257 最少拦截系统
  102 / 198 Problem J HDU 1160 FatMouse's Speed
  43 / 139 Problem K POJ 1015 Jury Compromise
  109 / 183 Problem L POJ 1458 Common Subsequence
  69 / 208 Problem M POJ 1661 Help Jimmy
  96 / 160 Problem N POJ 2533 Longest Ordered Subsequence
  79 / 135 Problem O POJ 3186 Treats for the Cows
  70 / 169 Problem P HDU 1078 FatMouse and Cheese
  67 / 137 Problem Q HDU 2859 Phalanx
  81 / 164 Problem R POJ 3616 Milking Time
  56 / 145 Problem S POJ 3666 Making the Grade
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

167 / 465 Problem A HDU 1024 Max Sum Plus Plus

d.已知有n个数,求m段不相交的子段权值之和最大

s.本题的大致意思为给定一个数组,求其分成m个不相交子段和最大值的问题。

设Num为给定数组,n为数组中的元素总数,Status[i][j]表示前i个数在选取第i个数的前提下分成j段的最大值,其中1<=j<=i<=n && j<=m,状态转移方程为:

Status[i][j]=Max(Status[i-1][j]+Num[i],Max(Status[0][j-1]~Status[i-1][j-1])+Num[i])

乍看一下这个方程挺吓人的,因为题中n的限定范围为1~1,000,000而m得限定范围没有给出,m只要稍微大一点就会爆内存。但仔细分析后就会发现Status[i][j]的求解只和Status[*][j]与Status[*][j-1]有关所以本题只需要两个一维数组即可搞定状态转移。

在进行更进一步的分析还会发现其实Max(Status[0][j-1]~Status[i-1][j-1])根本不需要单独求取。在求取now_Status(保存本次状态的数组)的过程中即可对pre_Status(保存前一次状态的数组)进行同步更新。

状态dp[i][j]
有前j个数,组成i组的和的最大值。
决策: 第j个数,是在第包含在第i组里面,还是自己独立成组。
方程 dp[i][j]=Max(dp[i][j-1]+a[j] , max( dp[i-1][k] ) + a[j] ) 0<k<j
空间复杂度,m未知,n<=1000000,  继续滚动数组。
时间复杂度 n^3. n<=1000000.  显然会超时,继续优化。
max( dp[i-1][k] ) 就是上一组 0....j-1 的最大值。我们可以在每次计算dp[i][j]的时候记录下前j个
的最大值 用数组保存下来  下次计算的时候可以用,这样时间复杂度为 n^2.
/*
状态dp[i][j]
由前j个数(包含第j个数),组成i组的和的最大值。
决策: 第j个数,是在第包含在第i组里面,还是自己独立成组。
方程 dp[i][j]=Max(dp[i][j-1]+a[j] , max( dp[i-1][k] ) + a[j] ) 0<k<j
空间复杂度,m未知,n<=1000000, 继续滚动数组。
时间复杂度 n^3. n<=1000000. 显然会超时,继续优化。
max( dp[i-1][k] ) 就是上一组 0....j-1 的最大值。我们可以在每次计算dp[i][j]的时候记录下前j个
的最大值 用数组保存下来 下次计算的时候可以用,这样时间复杂度为 n^2.
*/
#include<iostream>
#include<stdio.h>
using namespace std; #define MAXN 1000005 int s[MAXN]; int dp[MAXN];//dp[j]相当于dp[i][j]
int ma[MAXN];//ma[j]相当于dp[i-1][0]...dp[i-1][j]中的最大值 int main(){ int m,n;
int _ma;//分为i组时的最大值 while(~scanf("%d%d",&m,&n)){ for(int i=;i<=n;++i){
scanf("%d",&s[i]);
ma[i]=;//这样的初始化,好像比直接memset(ma,0,sizeof(ma))省点时间。
} ma[]=; for(int i=;i<=m;++i){ dp[i]=ma[i-]+s[i];//要分为i组,j最小为i,即dp[i][i]的初始化。它的值也就是前面i个数的和了。
_ma=dp[i];//分为i组时的最大值初始化 for(int j=i+;j<=n;++j){
dp[j]=max(dp[j-]+s[j],ma[j-]+s[j]);
ma[j-]=_ma;//上一次的值用完了,更新为这一次的值
_ma=max(_ma,dp[j]);//更新分为i组时的最大值
} } printf("%d\n",_ma); } return ;
}

234 / 372 Problem B HDU 1029 Ignatius and the Princess IV

d.找主元素

s.主元素即在数列中出现次数多于n/2的元素

我们很容易的看出来,在一个序列中如果去掉2个不同的元素,
那么原序列中的多元素,在新的序列中还是多元素,
因此我们只要按照序列依次扫描,先把t赋值给result,
增加个计数器,cnt = 1;然后向右扫描,
如果跟result相同,则cnt++,不同,那么cnt --,
这个真是我们从上面那个结论里得出的,一旦cnt == 0了,
那么必定c不是多元素,这个时候把t赋值为result,cnt = 1;,
重复该过程,知道结束,这个时候,result就是多元素,
这个的时间复杂度为n,该题本来可以用数组保存每个元素,
然后递归上述过程,可是,用数组超内存,
因此我们可以直接按照上述过程计算

#include<iostream>
#include<stdio.h>
using namespace std; int main(){ int N;
int t;
int i;
int ans;
int num; while(~scanf("%d",&N)){ num=; for(i=;i<N;++i){
scanf("%d",&t);
if(num==){
ans=t;
++num;
}
else{
if(t==ans){
++num;
}
else{
--num;
}
}
} printf("%d\n",ans); } return ;
}

161 / 259 Problem C HDU 1069 Monkey and Banana

d.n种箱子,长宽高分别为x,y,z,箱子有任意个,可以任意旋转,小箱子可以叠在大箱子上(上面的长宽要小于下面的),求可以叠起来的最大高度

s.把箱子的几种旋转情况分开,解决放任意多个的问题,然后相当于最长上升子序列,O(n^2)

#include<iostream>
#include<stdio.h>
#include<algorithm>
using namespace std; const int MAXN=;
struct Node{
int a,b,high;
int dp;//该箱子在最下面时的最大高度
}block[MAXN]; int tot;
void addBlock(int high,int a,int b){
block[tot].high=high;
block[tot].a=a;
block[tot].b=b;
block[tot].dp=high;
++tot;
} bool cmp(Node a,Node b){
if(a.a!=b.a)return a.a<b.a;
return a.b<b.b;
} int main(){ int n;
int x,y,z;
int i;
int j;
int ma_high;
int ca=; while(~scanf("%d",&n)){
if(n==){
break;
} tot=;
for(i=;i<n;++i){//把给出的block放置的所有可能放进block[]中,这样就可以解决有无限块的问题
scanf("%d%d%d",&x,&y,&z);
if(x==y&&y==z){//3个相等 x,x,x
addBlock(x,x,x);
}
else if(x==y){//2个相等 x,x,z
addBlock(z,x,x);
addBlock(x,x,z);
addBlock(x,z,x);
}
else if(x==z){// x,x,y
addBlock(y,x,x);
addBlock(x,x,y);
addBlock(x,y,x);
}
else if(y==z){// y,y,x
addBlock(x,y,y);
addBlock(y,y,x);
addBlock(y,x,y);
}
else{//都不相等 x,y,z
addBlock(x,y,z);
addBlock(x,z,y);
addBlock(y,x,z);
addBlock(y,z,x);
addBlock(z,x,y);
addBlock(z,y,x);
}
} sort(block,block+tot,cmp);
ma_high=;
for(i=;i<tot;++i){
for(j=;j<i;++j){
if(block[i].a>block[j].a&&block[i].b>block[j].b){
block[i].dp=max(block[i].dp,block[j].dp+block[i].high);
}
}
if(block[i].dp>ma_high){
ma_high=block[i].dp;
}
} printf("Case %d: maximum height = %d\n",++ca,ma_high);
} return ;
}

104 / 188 Problem D HDU 1074 Doing Homework

算法核心:状态压缩DP
大意:
有n门课程作业,每门作业的截止时间为D,需要花费的时间为C,若作业不能按时完成,每超期1天扣1分。
这n门作业按课程的字典序先后输入
问完成这n门作业至少要扣多少分,并输出扣分最少的做作业顺序
PS:达到扣分最少的方案有多种,请输出字典序最小的那一组方案

分析:
n<=15,由题意知,只需对这n份作业进行全排列,选出扣分最少的即可。
用一个二进制数存储这n份作业的完成情况,第1.。。。n个作业状况分别
对应二进制数的第0,1.。。。。,n-1位则由题意,故数字上限为2^n
其中 2^n-1即为n项作业全部完成,0为没有作业完成。。。

用dp[i]记录完成作业状态为i时的信息(所需时间,前一个状态,最少损失的分数)。
递推条件如下
1.状态a能做第i号作业的条件是a中作业i尚未完成,即a&i=0。
2.若有两个状态dp[a],dp[b]都能到达dp[i],那么选择能使到达i扣分小的那一条路径,若分数相同,转入3
3.这两种状态扣的分数相同,那么选择字典序小的,由于作业按字典序输入,故即dp[i].pre = min(a,b);

初始化:dp[0].cost = 0;dp[0].pre=-1;dp[0].reduced = 0;

最后dp[2^n-1].reduced即为最少扣分,课程安排可递归的输出

/*
HDU1074
*/
#include<stdio.h>
#include<string.h>
const int MAXN=<<;
struct Node
{
int cost;//所需时间
int pre;//前一状态
int reduced;//最少损失的分数
}dp[MAXN];
bool visited[MAXN];
struct Course
{
int deadtime;//截止日期
int cost;//所需日期
char name[];
}course[];
void output(int status)
{
int curjob=dp[status].pre^status;
int curid=;
curjob>>=;
while(curjob)
{
curid++;
curjob>>=;
}
if(dp[status].pre!=)
{
output(dp[status].pre);
}
printf("%s\n",course[curid].name);
}
int main()
{
int T,n,i,j;
scanf("%d",&T);
while(T--)
{
scanf("%d",&n);
int upper=<<n;
int dayupper=;
for(i=;i<n;i++)
{
scanf("%s%d%d",&course[i].name,&course[i].deadtime,&course[i].cost);
dayupper+=course[i].cost;
}
memset(visited,false,sizeof(visited));
dp[].cost=;
dp[].pre=-;
dp[].reduced=;//dp[0]是指所有作业都没有做的状态
visited[]=true;
int work;
int tupper=upper-;//tupper表示成二进制数是n个1的,表示所有的作业都完成了
for(j=;j<tupper;j++)//遍历所有状态
{
for(work=;work<n;work++)
{
int cur=<<work;
if((cur&j)==)//该项作业尚未做过
{
int curtemp=cur|j;
int day=dp[j].cost+course[work].cost;
dp[curtemp].cost=day;
int reduce=day-course[work].deadtime;
if(reduce<)reduce=;
reduce+=dp[j].reduced;
if(visited[curtemp])//该状态已有访问信息
{
if(reduce<dp[curtemp].reduced)
{
dp[curtemp].reduced=reduce;
dp[curtemp].pre=j;
}
//else if(reduce==dp[curtemp].reduced)
//扣分相同,取字典序小的那一个,由于这里j是按从小到达搜索的,默认已是按字典序,不需再处理
// {
// if(dp[curtemp].pre>j)
// dp[curtemp].pre = j;
// }
}
else //没有访问过
{
visited[curtemp]=true;
dp[curtemp].reduced=reduce;
dp[curtemp].pre=j;
}
}
}
}
printf("%d\n",dp[tupper].reduced);
output(tupper);//递归输出
}
}

大意:
有n门课程作业,每门作业的截止时间为D,需要花费的时间为C,若作业不能按时完成,每超期1天扣1分。
这n门作业按课程的字典序先后输入
问完成这n门作业至少要扣多少分,并输出扣分最少的做作业顺序
PS:达到扣分最少的方案有多种,请输出字典序最小的那一组方案

分析:
n<=15,由题意知,只需对这n份作业进行全排列,选出扣分最少的即可。
用一个二进制数存储这n份作业的完成情况,第1.。。。n个作业状况分别
对应二进制数的第0,1.。。。。,n-1位则由题意,故数字上限为2^n
其中 2^n-1即为n项作业全部完成,0为没有作业完成。。。

用dp[i]记录完成作业状态为i时的信息(所需时间,前一个状态,最少损失的分数)。
递推条件如下
1.状态a能做第i号作业的条件是a中作业i尚未完成,即a&i=0。
2.若有两个状态dp[a],dp[b]都能到达dp[i],那么选择能使到达i扣分小的那一条路径,若分数相同,转入3
3.这两种状态扣的分数相同,那么选择字典序小的,由于作业按字典序输入,故即dp[i].pre = min(a,b);

初始化:dp[0].cost = 0;dp[0].pre=-1;dp[0].reduced = 0;

最后dp[2^n-1].reduced即为最少扣分,课程安排可递归的输出

/*分析:对于n种家庭作业,全部做完有n!种做的顺序
但是n!太大了,且对于完成作业1,2,3和1,3,2和2,1,3和3,2,1和3,1,2来说
完成它们消耗的天数一定是一样的,只是完成的顺序不同从而扣的分不同
所以可以将完成相同的作业的所有状态压缩成一种状态并记录扣的最少分即可
即:状态压缩dp
对于到达状态i,从何种状态到达i呢?只需要枚举所有的作业
假如对于作业k,i中含有作业k已完成,那么i可以由和i状态相同的状态仅仅是k未完成的
状态j=i-(1<<k)来完成k到达,并且j一定比i小,如果状态从0枚举到2^n-1那么j一定是在i之前已经计算过的
*/
#include <bits/stdc++.h>
#define INF 0x3f3f3f3f
#define MAX ( (1<<15)+10 )
typedef long long LL;
using namespace std; ///课程信息 可以封装成结构体
int deadT[],cost[];
char s[][]; ///DP的中间状态信息,可以封装成结构体
int dp[MAX],tim[MAX],pre[MAX];///dp[i]记录到达状态i扣的最少分,t是在dp[i](扣除最小分)相应的花去多少天了 void output(int x){
if(!x)return;
output(x-(<<pre[x]));
printf("%s\n",s[pre[x]]);
} int main(){
int T,n;
scanf("%d",&T);
while(T--){
scanf("%d",&n);
for(int i=;i<n;++i)scanf("%s%d%d",&s[i],&deadT[i],&cost[i]);
int bit=<<n ;
for(int i=;i<bit;++i)///枚举到达状态i
{
dp[i]=INF;///初始化到达状态i的扣分
for(int j=n-;j>=;--j){///由于输入时按字符大小输入,而每次完成j相当于把j放在后面完成
///这里下面判断是dp[i]>dp[i-temp]+score,所以是n-1开始
///如果下面判断是dp[i]>=dp[i-temp]+score则从0开始
int temp=<<j;
if(!(i&temp)) continue;///状态i不存在作业j完成则不能通过完成作业j到达状态i
int score=tim[i-temp]+cost[j]-deadT[j];///i-temp表示没有完成j的那个状态 score是在当前情形下完成j所扣除的分数
if(score<)score=;///完成j被扣分数最小是0
if(dp[i]>dp[i-temp]+score){
dp[i]=dp[i-temp]+score;
tim[i]=tim[i-temp]+cost[j];///到达状态i花费的时间
pre[i]=j;///到达状态i的前驱,为了最后输出完成作业的顺序
}
}
}
printf("%d\n",dp[bit-]);
output(bit-);///输出完成作业的顺序
}
return ;
}

153 / 248 Problem E HDU 1087 Super Jumping! Jumping! Jumping!

d.找和最大的上升子序列

#include<iostream>
#include<stdio.h>
using namespace std; int main(){ int N;
int v[];
int dp[];//dp[i]表示以a[i]结束的最大的上升子序列的和
int i;
int ma;
int j; while(~scanf("%d",&N)){
if(N==){
break;
}
for(i=;i<N;++i){
scanf("%d",&v[i]);
} ma=;
for(i=;i<N;++i){
dp[i]=v[i];
for(j=;j<i;++j){
if(v[i]>v[j]){
dp[i]=max(dp[i],dp[j]+v[i]);
}
}
if(dp[i]>ma){
ma=dp[i];
}
} printf("%d\n",ma);
} return ;
}

127 / 215 Problem F HDU 1114 Piggy-Bank

d.给出一个存钱罐的容量,给出n种硬币的价值p和重量w(注意:每种硬币可无限取)

1.如果存钱罐能够正好塞满,输出塞满存钱罐需要的最少硬币的价值。

2.若不能完全塞满,则输出impossible。

s.每种物品可以放无限多次。所以为完全背包问题。此题是求最小值,为完全背包的变形。

注意初始化 dp[ 0 ]=0;

for i=1..N

for v=0..V

f[v]=max{f[v],f[v-cost]+weight}

#include<iostream>
#include<stdio.h>
#include<string.h>
using namespace std; int main(){ int T;
int E,F;
int N;
int P[],W[];
int v;//背包大小
int dp[];//dp[i]表容量为i的时候所装东西的最小价值
int i,j; scanf("%d",&T); while(T--){
scanf("%d%d",&E,&F);
v=F-E;
scanf("%d",&N);
for(i=;i<N;++i){
scanf("%d%d",&P[i],&W[i]);
} memset(dp,0x3f,sizeof(dp));
dp[]=;
for(i=;i<N;++i){
for(j=W[i];j<=v;++j){
dp[j]=min(dp[j],dp[j-W[i]]+P[i]);
}
} if(dp[v]==0x3f3f3f3f){
printf("This is impossible.\n");
}
else{
printf("The minimum amount of money in the piggy-bank is %d.\n",dp[v]);
} } return ;
}

151 / 428 Problem G HDU 1176 免费馅饼

d.在0~10这11个位置上,某时刻某位置会掉落馅饼,gameboy在5位置,每次只能移动1,求他能接到的最大馅饼数。

#include<iostream>
#include<stdio.h>
#include<string.h>
using namespace std; int dp[][];//dp[i][j]表示第i秒j位置可得的最大值 int main(){ int n;
int x,T;
int i;
int j;
int ma_t; while(~scanf("%d",&n)){
if(n==){
break;
} memset(dp,,sizeof(dp));
ma_t=;
for(i=;i<n;++i){
scanf("%d%d",&x,&T);
++dp[T][x];
if(T>ma_t){
ma_t=T;
}
} for(i=ma_t-;i>=;--i){
dp[i][]=dp[i][]+max(dp[i+][],dp[i+][]);
for(j=;j<;++j){
dp[i][j]=dp[i][j]+max(max(dp[i+][j-],dp[i+][j]),dp[i+][j+]);
}
dp[i][]=dp[i][]+max(dp[i+][],dp[i+][]);
} printf("%d\n",dp[][]);
} return ;
}

118 / 199 Problem H HDU 1260 Tickets

d.K个人排队买票,单人买票花费Si时间,相邻两人一起买票花费Di时间,求售票所需最少时间。

s.dp[i]表示前i个人买票所需最少时间

dp[i]=min(dp[i-1]+S[i],dp[i-2]+D[i]);

#include<iostream>
#include<stdio.h>
using namespace std; const int MAXN=; int S[MAXN];//单人买票所需时间
int D[MAXN];//相邻两人买票所需时间
int dp[MAXN];//dp[i]表示前i个人买票所需最少时间 int main(){ int N;
int K;
char str_h[],str_m[],str_s[];
int h,m,s;
int i; scanf("%d",&N); while(N--){
scanf("%d",&K);
for(i=;i<=K;++i){
scanf("%d",&S[i]);
}
for(i=;i<=K;++i){
scanf("%d",&D[i]);
} dp[]=;
dp[]=S[];
for(i=;i<=K;++i){
dp[i]=min(dp[i-]+S[i],dp[i-]+D[i]);
} h=dp[K]//+;
m=(dp[K]/)%;
s=dp[K]%;
while(h>=){
h-=;
} if(h<=){
str_h[]=h/+'';str_h[]=h%+'';str_h[]='\0';
str_m[]=m/+'';str_m[]=m%+'';str_m[]='\0';
str_s[]=s/+'';str_s[]=s%+'';str_s[]='\0';
printf("%s:%s:%s am\n",str_h,str_m,str_s);
}
else{
h-=;
str_h[]=h/+'';str_h[]=h%+'';str_h[]='\0';
str_m[]=m/+'';str_m[]=m%+'';str_m[]='\0';
str_s[]=s/+'';str_s[]=s%+'';str_s[]='\0';
printf("%s:%s:%s pm\n",str_h,str_m,str_s);
}
} return ;
}

167 / 292 Problem I HDU 1257 最少拦截系统

这道题我已经做过了,也在博客上写过了。。。
但是很久以后重做此题,发现这题以前的做法是错误的。
同时在网上也看到了一些别人的题解,发现都是错的。
说明杭电上此题的数据是弱爆了。。。。。。。。。
 
感觉就是贪心。。。。。就是每出现一个导弹, 用当前距离最近的拦截系统去拦截。。不能拦截了就新开一个拦截系统。同时用数组记录每个拦截系统当前的高度。。
 
具体看代码,,很简单
/*
HDU 1257
为了使得使用的拦截系统最少,自然是要考虑使用与当前高度最接近的系统拦截(应该是贪心算法)
*/
#include<stdio.h>
#define INF 0x7ffffff
#define MAXN 10000
int dp[MAXN];//dp[i]代表第i个导弹当前拦截的高度
int main()
{
int n,x,i,res,flag;
int minh;
while(scanf("%d",&n)!=EOF)
{
res=;
while(n--)
{
scanf("%d",&x);
flag=;
minh=INF;
int tempi;
for(i=;i<res;i++)
{
if(x<=dp[i]&&minh>dp[i]-x)
{
minh=dp[i]-x;
//dp[i]=x;
tempi=i;
flag=;
}
}
if(flag==)
{
dp[res]=x;
res++;
}
else dp[tempi]=x;
}
printf("%d\n",res);
}
return ;
}

ps : 上面代码找最小的高度的时候找到第一个应该就是最小的

ps : 这个题好像用最长上升子序列也可以啊,但是要知道Dilworth定理

#include<stdio.h>
#include<string.h>
#include<math.h>
#include <iostream>
#include <algorithm>
using namespace std;
int N,sum=;
int a[],dp[];
int main()
{
cin.tie();
cin.sync_with_stdio(false);
// freopen("in.txt","r",stdin);
while(cin>>N)
{
// memset(a,0,sizeof(a));
memset(dp,,sizeof(dp));
int numbs=;
for(int i=;i<=N;i++)
{
cin>>a[i];
}
for(int i=;i<=N;i++)
{
for(int j=i-;j>=;j--)
{
if(a[i]>a[j])
{
dp[i]=max(dp[i],dp[j]+);
}
}
if(numbs<dp[i])
numbs=dp[i];
}
cout<<numbs<<endl;
}
return ;
}

另外:

刚开始以为只要一遍一遍的找最长非递增数列就行了,第一遍找出来最长的,然后把这一列里面的数字去掉,在剩下来的里面继续前面的操作就行了,直到所有元素都被去掉,然后发现碰到这种情况就不行了

9 5 1 8 4 2 第一次去掉9 5 4 2, 然后去掉1,最后去掉8,就是说要三次,其实只要两次 :第一次9 5 1 ,第二次8 4 2;

102 / 198 Problem J HDU 1160 FatMouse's Speed

最长上升子序列

/*
HDU 1160
*/
#include<stdio.h>
#include<algorithm>
using namespace std;
#define MAXN 1000
struct Node
{
int w,s;//重量和速度
int index;//最初的序号,避免排序后乱掉顺序,后面需要输出的
}mouse[MAXN+];
bool cmp(Node a,Node b)//先按照w从小到大排序,再按照y从大到小排序
{
if(a.w<b.w) return ;
else if(a.w==b.w&&a.s>b.s)return ;
else return ;
}
int dp[MAXN+];//dp[i]表示以第i个数据结尾的符合要求的子列长度
int pre[MAXN+];//记录i对应的上一个数据
int res[MAXN+];//存放最终结果下标
int main()
{
//freopen("test.in","r",stdin);
//freopen("test.out","w",stdout);
int i=,j;
while(scanf("%d%d",&mouse[i].w,&mouse[i].s)!=EOF)
{
dp[i]=;
pre[i]=;
mouse[i].index=i;
i++;
}
int n=i-;
sort(mouse+,mouse++n,cmp);
int maxlen=;//最长序列长度
int maxi;//最长序列的最后一个数下标
dp[]=;
for(i=;i<=n;i++)
{
for(j=;j<i;j++)
if(mouse[i].w>mouse[j].w&&mouse[i].s<mouse[j].s&&dp[j]+>dp[i])
{
dp[i]=dp[j]+;
pre[i]=j;
if(dp[i]>maxlen)
{
maxi=i;
maxlen=dp[i];
}
}
}
int t=maxi;
i=;
while(t!=)
{
res[i++]=t;
t=pre[t];
}
printf("%d\n",i);
while(i>)
{
i--;
printf("%d\n",mouse[res[i]].index);
}
return ;
}

43 / 139 Problem K POJ 1015 Jury Compromise
109 / 183 Problem L POJ 1458 Common Subsequence

/*
POJ1458Common Subsequence */
#include<stdio.h>
#include<algorithm>
#include<string.h>
using namespace std;
#define MAXN 210
char str1[MAXN];
char str2[MAXN];
int dp[MAXN][MAXN];
int solve()
{
int len1=strlen(str1);
int len2=strlen(str2);
int i,j;
for(i=;i<=len1;i++) dp[i][]=;
for(i=;i<=len2;i++) dp[][i]=;
for(i=;i<len1;i++)
for(j=;j<len2;j++)
{
if(str1[i]==str2[j]) dp[i+][j+]=dp[i][j]+;
else
{
dp[i+][j+]=max(dp[i][j+],dp[i+][j]);
}
}
return dp[len1][len2];
}
int main()
{
while(scanf("%s%s",&str1,&str2)!=EOF)
{ printf("%d\n",solve());
}
return ;
}

69 / 208 Problem M POJ 1661 Help Jimmy

当Jimmy落在一个平台上后有两种选择(向左走或向右走),而Jimmy走到平台左边和右边的时间很容易计算,如果我们得到了以平台左边为起点及以平台右边为起点到地面的最短时间,那么选择往左走还是往右走就很容易了。这样,原问题就分解为两个子问题这两个子问题和原问题的形式是一致的了,也就找到了“状态”dp[i][j], j = 0, 1 (dp[i][0]表示以i号平台左边为起点到地面的最短时间,dp[i][1]]表示以i号平台右边为起点到地面的最短时间),而“状态转移方程”如下:

dp[i][0] = H[i] - H[m] + Min (dp[m][0] + X1[i] - X1[m], dp[m][1] + X2[m] - X1[i]);  m为i左边下面的平台的编号

dp[i][1] = H[i] - H[m] + Min (dp[m][0] + X2[i] - X1[m], dp[m][1] + X2[m] - X2[i]);  m为i右边下面的平台的编号

/*
POJ1661 Help Jimmy
动态规划学习
*/
#include<stdio.h>
#include<stdlib.h>
#include<algorithm>
#include<iostream>
#define MAXN 1010
const int INF=;
using namespace std;
int dp[MAXN][];
//dp[i][0]表示从平台i左侧到地面的时间,
//dp[i][1]表示从平台i右侧到地面的时间
struct Node
{
int lx,rx,h;
}line[MAXN];
int cmp(const void *a,const void *b)//按照高度从大到小排序
{
Node *c,*d;
c=(Node *)a;
d=(Node *)b;
return d->h-c->h;
}
int main()
{
//freopen("test.in","r",stdin);
//freopen("test.out","w",stdout);
int T,N,X,Y,MAX;
int i,j;
scanf("%d",&T);
while(T--)
{
scanf("%d %d %d %d",&N,&X,&Y,&MAX);
line[].lx=X;
line[].rx=X;
line[].h=Y;
for(i=;i<=N;i++)
scanf("%d%d%d",&line[i].lx,&line[i].rx,&line[i].h);
qsort(line,N+,sizeof(line[]),cmp);
dp[N][]=line[N].h;
dp[N][]=line[N].h;
for(i=N-;i>=;i--)
{
for(j=i+;j<=N;j++)
if(line[j].lx<=line[i].lx&&line[j].rx>=line[i].lx)
break;
if(j<=N)
{
if(line[i].h-line[j].h>MAX) dp[i][]=INF; else
{
int t1=line[i].h-line[j].h+line[i].lx-line[j].lx+dp[j][];
int t2=line[i].h-line[j].h+line[j].rx-line[i].lx+dp[j][];
dp[i][]=min(t1,t2);
}
}
else
{
if(line[i].h>MAX)dp[i][]=INF;
else dp[i][]=line[i].h;
} for(j=i+;j<=N;j++)
if(line[j].lx<=line[i].rx&&line[j].rx>=line[i].rx) break;
if(j<=N)
{
if(line[i].h-line[j].h>MAX) dp[i][]=INF;
else
{
int t1=line[i].h-line[j].h+line[i].rx-line[j].lx+dp[j][];
int t2=line[i].h-line[j].h+line[j].rx-line[i].rx+dp[j][];
dp[i][]=min(t1,t2);
}
}
else
{
if(line[i].h>MAX) dp[i][]=INF;
else dp[i][]=line[i].h;
}
}
printf("%d\n",dp[][]);
}
return ;
}

96 / 160 Problem N POJ 2533 Longest Ordered Subsequence

/*
POJ2533Longest Ordered Subsequence
最长上升子序列
描述:给一队排列整齐的数列ai,找到数列ai中最长上升子序列。
输入:第一行是数列的元素个数N,第二行是N个整数,范围从0到10,000。(1<=N<=1000)
输出:包括一个整数,表示最长上升子序列的长度。 Sample Input
1 7 3 5 9 4 8 Sample Output */
/*
用动态规划做。
用dp[k]表示以a[k]作为终点的最大上升子序列
则:
dp[1] = 1;
dp[k] = Max (dp[i]:1 <= i < k 且 a[i ]< a[k] 且 k != 1) + 1.
*/
#include<stdio.h>
#define MAXN 1000
int dp[MAXN+],a[MAXN+];//a数组记录输入的序列
int main()
{
int n,i,j;
scanf("%d",&n);
for(i=;i<=n;i++)
scanf("%d",&a[i]);
dp[]=;
for(i=;i<=n;i++)
{
int temp=;
for(j=;j<i;j++)
if(a[i]>a[j])
if(temp<dp[j])
temp=dp[j];
dp[i]=temp+;
}
int maxlen=;
for(i=;i<=n;i++)
if(maxlen<dp[i])
maxlen=dp[i];
printf("%d\n",maxlen);
return ;
}

79 / 135 Problem O POJ 3186 Treats for the Cows

n个数排成一串,每次可以从头取一个数或者从尾取一个数,取出数后该数的值乘以取他的时间(每取一个数时间加一)是价值,问能够得到的最大价值。

/*
POJ 3186
*/
#include<stdio.h>
#include<iostream>
#include<string.h>
#include<algorithm>
using namespace std;
const int MAXN=;
int a[MAXN];
int dp[MAXN][MAXN];
int main()
{
int n;
while(scanf("%d",&n)!=EOF)
{
for(int i=;i<=n;i++)
scanf("%d",&a[i]);
memset(dp,,sizeof(dp));
for(int i=;i<=n;i++)
dp[i][i]=a[i]*n;
for(int k=;k<n;k++)
for(int i=;i+k<=n;i++)
{
int j=i+k;
dp[i][j]=max(dp[i+][j]+(n-k)*a[i],dp[i][j-]+(n-k)*a[j]);
}
printf("%d\n",dp[][n]);
}
return ;
}
//dp,要从内向外推,dp[i][j]表示串中只剩下i~j数的时候最大价值
//dp[i][j]=max(dp[i+1][j]+a[i]*cnt,dp[i][j-1]+a[j]*cnt),因为dp[i][j]时要用到
//后面的数,要从后向前求dp。
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int n,a[];
int f[][];
int main()
{
while(scanf("%d",&n)==){
for(int i=;i<n;i++) scanf("%d",&a[i]);
for(int i=n-;i>=;i--){
for(int j=i;j<n;j++){
f[i][j]=max(f[i+][j]+a[i]*(n+i-j),f[i][j-]+a[j]*(n+i-j));
}
}
printf("%d\n",f[][n-]);
}
return ;
}

70 / 169 Problem P HDU 1078 FatMouse and Cheese

老鼠每次最多走k步停下来,停下的这个位置只能比上一个停留的位置大,并获取其价值,每次只能水平或垂直走,问最大能得到的价值

#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std; int n,k,dp[][],a[][];
int to[][] = {,,-,,,,,-}; int check(int x,int y)
{
if(x< || y< || x>n || y>n)
return ;
return ;
} int dfs(int x,int y)
{
int i,j,l,ans = ;
if(!dp[x][y])
{
for(i = ; i<=k; i++)
{
for(j = ; j<; j++)
{
int xx = x+to[j][]*i;
int yy = y+to[j][]*i;
if(check(xx,yy))
continue;
if(a[xx][yy]>a[x][y])
ans = max(ans,dfs(xx,yy));
}
}
dp[x][y] = ans+a[x][y];
}
return dp[x][y];
} int main()
{
int i,j;
while(~scanf("%d%d",&n,&k),n>&&k>)
{
for(i = ; i<=n; i++)
for(j = ; j<=n; j++)
scanf("%d",&a[i][j]);
memset(dp,,sizeof(dp));
printf("%d\n",dfs(,));
} return ;
}
#include<stdio.h>
#include<algorithm>
#include<string.h>
#include<iostream>
using namespace std;
const int MAXN=;
int a[MAXN][MAXN];
int dp[MAXN][MAXN]; struct Node
{
int x,y;
int val;
}node[MAXN*MAXN];
bool cmp(Node a,Node b)
{
return a.val<b.val;
} int main()
{
int n,k;
while(scanf("%d%d",&n,&k)==)
{
if(n==-&&k==-)break;
int cnt=;
for(int i=;i<n;i++)
for(int j=;j<n;j++)
{
scanf("%d",&a[i][j]);
if(i!=||j!=)
{
node[cnt].x=i;
node[cnt].y=j;
node[cnt++].val=a[i][j];
}
}
sort(node,node+cnt,cmp);
memset(dp,-,sizeof(dp));
dp[][]=a[][];
int ans=dp[][];
for(int i=;i<cnt;i++)
{
int x=node[i].x;
int y=node[i].y; for(int xx=max(,x-k);xx<=min(n-,x+k);xx++)
{
if(a[xx][y]>=a[x][y])continue;
if(dp[xx][y]==-)continue;
dp[x][y]=max(dp[x][y],dp[xx][y]+a[x][y]);
}
for(int yy=max(,y-k);yy<=min(n-,y+k);yy++)
{
if(a[x][yy]>=a[x][y])continue;
if(dp[x][yy]==-)continue;
dp[x][y]=max(dp[x][y],dp[x][yy]+a[x][y]);
}
ans=max(ans,dp[x][y]);
}
printf("%d\n",ans);
}
return ;
}

67 / 137 Problem Q HDU 2859 Phalanx

题意:输入n,然后输入n行n列的字符。求这个矩阵中子矩阵是关于左下角到右上角这条线对称的最大矩阵边。

解析:枚举每一个点作为对称轴的左下角,然后从这一点分别向上和向右寻找,知道找到一个不相等的字符,或者这个点越界,停止。

如果这个矩阵比以这个点右上角的点大,那么更新dp[i][j]=dp[i-1][j+1]+1.否则dp[i][j]等于这个矩阵的边。

#include <iostream>
#include <queue>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <stack>
#include <algorithm> using namespace std; const int maxn = ; char str[maxn][maxn];
int dp[maxn][maxn]; int main()
{
int n;
while(scanf("%d", &n),n)
{
for(int i=; i<=n; i++)
{
scanf("%s", str[i]+);
}
memset(dp, , sizeof(dp));
int Max = ;
for(int i=; i<=n; i++)
{
for(int j=; j<=n; j++)
{
if(i==)
dp[i][j] = ;
else
{
int up = i, right = j;
while(str[up][j]==str[i][right])
{
up--;
right++;
if(up<||right>n)
break;
}
int x = i - up;
if(x>dp[i-][j+])
dp[i][j] = dp[i-][j+]+;
else
dp[i][j] = x;
Max = max(Max, dp[i][j]);
}
}
}
printf("%d\n", Max);
} return ;
}

81 / 164 Problem R POJ 3616 Milking Time

在一个农场里,在长度为N个时间可以挤奶,但只能挤M次,且每挤一次就要休息t分钟;

接下来给m组数据表示挤奶的时间与奶量求最大挤奶量

本题其实很简单的,一个简单的动态规划,用一个dp表示在第i个时间段挤奶量的最大值,从i+1更新到M

不要忘记排序

#include <cstdio>
#include <cstring>
#include <vector>
#include <iostream>
#include <algorithm>
#include <limits.h>
#include <cmath>
#include <queue>
using namespace std;
int dp[];
struct sa {
int x,y,sum;
} p[];
int cmp(const sa a,const sa b)
{
if(a.x==b.x) {
return a.y<b.y;
}
return a.x<b.x;
}
int main()
{
int n,m,t;
scanf("%d%d%d",&n,&m,&t);
for(int i=; i<m; i++) {
scanf("%d%d%d",&p[i].x,&p[i].y,&p[i].sum);
p[i].y+=t;
}
sort(p,p+m,cmp);//这一步很关键,时间是有顺序的
// for(int i=0;i<m;i++)
// cout<<p[i].x<<" "<<p[i].y<<endl;
for(int i=m-; i>=; i--) {
dp[i]=p[i].sum;
for(int j=i+; j<m; j++)
if(p[j].x>=p[i].y) {
dp[i]=max(dp[i],dp[j]+p[i].sum);//这里就是转移方程
// cout<<j<<" "<<dp[j]<<endl;
}
}
int maxx=;
for(int i=; i<m; i++) {
maxx=max(maxx,dp[i]);
}
cout<<maxx<<endl;
return ;
}

56 / 145 Problem S POJ 3666 Making the Grade

不懂为什么那样离散化,见题解吧。