题目
1、入门
(1) 摘花生
【题目描述】Hello Kitty想摘
点花生送给她喜欢的米老鼠。她来到一片有网格状道路的矩形花生地(如右图),从西北角进去,东南角出来。
地里每个道路的交叉点上都有种着一株花生苗,上面有若干颗花生,经过一株花生苗就能摘走该它上面所有的花生。Hello
Kitty只能向东或向南走,不能向西或向北走。问Hello Kitty最多能够摘到多少颗花生。
【输入格式】
第一行是一个整数T,代表一共有多少组数据。接下来是T组数据。
每组数据的第一行是两个整数,分别代表花生苗的行数R和列数 C。
每组数据的接下来R行数据,从北向南依次描述每行花生苗的情况。每行数据有C个整数,按从西向东的顺序描述了该行每株花生苗上的花生数目M。
【输出格式】 对每组输入数据,输出一行,内容为Hello Kitty能摘到得最多的花生颗数。
样例
input
2
2 2
1 1
3 4
2 3
2 3 4
1 6 5
output
8
16
【分析】这道题作为dp的入门题,非常的简单啊!因为HK只能向右或向下,有明显顺序,那么dp的阶段就可以是HK的位置。我们显然知道HK位于点 [ i ] [ j ] [ i ][ j ] [i][j]时所摘到的花生的最大值只和 [ i − 1 ] [ j ] [ i-1 ][ j ] [i−1][j]与 [ i ] [ j − 1 ] [ i ][ j-1 ] [i][j−1]有关。可以用表示Hk位于点[ i ][ j ]时所摘到的花生的最大值。
状态转移方程为 f [ i ] [ j ] = m a x ( f [ i − 1 ] [ j ] , [ i ] [ j − 1 ] ) + a [ i ] [ j ] f[ i ][ j ]=max( f[ i-1 ][ j ] ,[ i ][ j-1 ] )+a[ i ][ j ] f[i][j]=max(f[i−1][j],[i][j−1])+a[i][j]。( a [ i ] [ j ] a[ i ][ j ] a[i][j] 表示每个位置本身的花生数),本题得到解决。
具体代码如下:
#include<bits/stdc++.h>
using namespace std;
int t,r,c;
int a[110][110],f[110][110];
void Pick_up(){
f[1][1]=a[1][1];
for(int i=1;i<=r;i++)
for(int j=1;j<=c;j++)
f[i][j]=max(f[i][j-1],f[i-1][j])+fd[i][j];
printf("%d\n",f[r][c]);
}
int main(){
scanf("%d",&t);
for(int i=1;i<=t;i++){
memset(a,0,sizeof(a));
memset(f,0,sizeof(f));
scanf("%d%d",&r,&c);
for(int i=1;i<=r;i++)
for(int j=1;j<=c;j++)
scanf("%d",&a[i][j]);
Pick_up();
}
}
(2)黑熊过河
【题目描述】晶晶的爸爸给晶晶出了一道难题:有一只黑熊想过河,但河很宽,黑熊不会游泳,只能借助河面上的石墩跳过去。
它可以一次跳一墩,也可以一次跳两墩,但是每跳一次都会耗费一定的能量,黑熊最终可能因能量不够而掉入水中。所幸的是,有些石墩上放了一些食物,一些食物可以给黑熊增加一定的能量。问黑熊能否利用这些石墩安全地抵达对岸?请计算出抵达对岸后剩余能量的最大值。
【输入格式】
第1行包含两个整数和(0≤≤1000),其中P表示黑熊的初始能量,Q表示黑熊每次起跳时耗费的能量。第2行只有一个整数(1≤≤10^6),表示河中石墩的数目。
第3行有个整数,表示每个石墩上食物的能量值(0≤≤1000)。
【输出格式】
输出1行,若黑熊能抵达对岸,输出抵达对岸后剩余能量的最大值;若不能,则输出“NO”。【样例数据】
input12 5
5
0 5 2 0 7
output
6
【分析】这道题也是非常的简单啊! 看到“跳一墩”、“跳两墩”这样的字样,我们一定会想到之前学习的递归、递推、记忆化搜索。一般dp选用递推或记忆化搜索。可以以黑熊的位置划分阶段。用 f [ i ] f[ i ] f[i]表示黑熊位于点i时的剩余能量最大值。
状态转移方程为: f [ i ] = m a x ( f [ i − 1 ] , f [ i − 2 ] ) − q + a [ i ] f[ i ]=max(f[ i-1 ] , f[ i-2 ])-q+a[i] f[i]=max(f[i−1],f[i−2])−q+a[i]。 本题得到解决。
具体代码如下:
#include<bits/stdc++.h>
using namespace std;
int p,q,n,a[1000010],f[1000010];
int main(){
scanf("%d%d%d",&p,&q,&n);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
dp[0]=p; dp[1]=p-q+a[1];
for(int i=2;i<=n+1;i++){
if(f[i-1]<q&&f[i-2]<q){
cout<<"NO"<<endl;
return 0;//连续两个为负黑熊就gg了
}
f[i]=max(f[i-1],f[i-2])-q+a[i];//状态转移
}
cout<<f[n+1]<<endl;
}
2、要素与动机
(1)[USACO06JAN]Dollar Dayz S
【题目描述】FJ 到商场买工具。商场里有 K 种工具(1≤K≤100),价格分别为 1,2,…,K 元。FJ 手里有 N 元(1≤N≤1000),必须花完。他有多少种购买方案呢?
【输入格式】 一行两个整数 。【输出格式】 输出不同的购买方案数。
【样例数据】
input
5 3
output
5
【分析】题目描述很简单,但根据我们小学二年级就学过的真理,我们可以知道,简单的题往往会在你没注意到的地方痛击你。根据dp求什么设什么的普遍思路,我们可以设 f [ i ] f[ i ] f[i]为花i元钱的选择数,则 f [ i ] = f [ i ] + f [ i − j ] f[ i ]=f[ i ]+f[ i-j ] f[i]=f[i]+f[i−j]。本题得到解决。。。等等,为什么只得了 70 70 70分。再看数据规模,不仅爆 i n t int int, l o n g long long l o n g long long也会爆掉。所以想要解决本题,需要用到高精度。(集训完一定补 Q A Q QAQ QAQ
70 70 70分无高精度代码如下:
#include<bits/stdc++.h>
using namespace std;
long long f[1010],k,n;
int main(){
cin>>n>>k;//总共n元,k种选择
f[0]=1;
for(int i=1;i<=k;i++)
for(int j=i;j<=n;j++)
f[j]=f[j]+f[j-i];
cout<<f[n]<<endl;
return 0;
}
做了这么多道题,我们对dp已经有了一个初步的了解,当然这只是冰山一角。
dp的基本做题流程为:
1.划分阶段 一般以时间、位置等某种题目已知元素为阶段。
2.通过阶段确定状态表达 即f[i][j]表示什么。
3.找出已知状态,初始化。
4.写出状态表达方程
5.找出答案
(2)[递推练习]石头剪刀布
【题目描述】小菜给出一个自然数n,小明和小头轮流操作,每次操作可以把n减去n所拥有的数字中的最大值或者最小值(不包括0),不能操作者输(即当n=0时)。小菜怕他们不懂,于是给了一个例子n=1024,则最大值=4,最小值=1。假如小明先操作,那么他可以留给小头1023或者1020。小明和小头觉得这个游戏很好玩,于是打算多玩几局。每次由小明先操作,如果两人都是用最佳策略,请问谁会赢?
【输入格式】 第一行只有一个整数t,表示玩t轮游戏 接下来t行,每行一个自然数n,表示初始时小菜给出的自然数。 【输出格式】
共t行,如果小明赢了第i轮游戏则在第i行输出“YES” 否则输出“NO” 【样例数据】
input
2
9
10
output
YES
NO
【分析】本题涉及点博弈论,但是我们不关心。所谓的最佳策略就是在自己有赢的可能的情况下让自己赢,使原来的概率事件变为必然事件,因此可以用一个bool数组记录游戏结果。
令 f [ i ] f[ i ] f[i] 表示数字为i时能否胜利。由题意易知 f [ 0 ] = 1 f[0]=1 f[0]=1,且当 i i i为个位数时, f [ i ] f[ i ] f[i]的值都为 0 0 0。
我们只需要额外记录一下查询数的最大最小值 m a x x 、 m i n n maxx、minn maxx、minn就可以了。
状态转移方程: f [ i ] = ! f [ i − m i n n ] ∣ ∣ ! f [ i − m a x x ] f[ i ]=!f[ i-minn ]||!f[ i-maxx ] f[i]=!f[i−minn]∣∣!f[i−maxx](只要 i − m i n n i-minn i−minn和 i − m a x x i-maxx i−maxx中有一个为0,那么