在上一篇我们对记忆化搜索的概念含义做了介绍,并举了几个基础了例子,没看过上一篇的请点击记忆化搜索题目总结(1),这篇我们来介绍几个相对复杂的例子。
洛谷P1280 题目链接
尼克每天上班之前都连接上英特网,接收他的上司发来的邮件,这些邮件包含了尼克主管的部门当天要完成的全部任务,每个任务由一个开始时刻与一个持续时间构成。
尼克的一个工作日为N分钟,从第一分钟开始到第N分钟结束。当尼克到达单位后他就开始干活。如果在同一时刻有多个任务需要完戍,尼克可以任选其中的一个来做,而其余的则由他的同事完成,反之如果只有一个任务,则该任务必需由尼克去完成,假如某些任务开始时刻尼克正在工作,则这些任务也由尼克的同事完成。如果某任务于第P分钟开始,持续时间为T分钟,则该任务将在第P+T-1分钟结束。
写一个程序计算尼克应该如何选取任务,才能获得最大的空暇时间。
思路:
定义work[i][1]和work[i][2]为每个任务的起始和结束时间,
next[i]为第i时刻货之后应紧接着的下个任务号,
dp[i]为第i时刻到最后的最大空余时间
状态转移方程为:dp[i] = max(dp[ work[j][2] + 1] + work[j][1] - i),其中work[j][1] = work[next[i]][1]。
#include
#include
using namespace std;
int work[10001][3]; //每个任务的开始和结束时间
int next[10001]; //某一时刻或之后应紧接着下个任务
int dp[10001]; //第i时刻到最后的最大空余时间
int n, k;
int dfs(int t)
{
if(next[t] > k){ //若此时刻之后无任务,返回n-t+1为空余时间
return n - t + 1;
}
if(dp[t] != -1){
return dp[t];
}
int j = next[t];
dp[t] = 0;
//枚举此时刻可以开始的同一时刻任务
while(work[j][1] == work[next[t]][1])
{
int ans = dfs(work[j][2] + 1);
if(dp[t] < ans + work[j][1] - t){
dp[t] = ans + work[j][1] - t;
}
j++;
}
return dp[t];
}
int main()
{
int i, j;
cin >> n >> k;
for(i=1; i<=k; i++)
{
int a, b;
cin >> a >> b;
work[i][1] = a;
work[i][2] = a + b - 1;
}
j = 1;
for(i=1; i<=n; i++)
{
while(i > work[j][1] && j <= k)
{
j++;
}
next[i] = j;
}
memset(dp, -1, sizeof(dp));
cout << dfs(1);
return 0;
}
蓝桥杯历届试题 题目链接
X 国王有一个地宫宝库。是 n x m 个格子的矩阵。每个格子放一件宝贝。每个宝贝贴着价值标签。
地宫的入口在左上角,出口在右下角。
小明被带到地宫的入口,国王要求他只能向右或向下行走。
走过某个格子时,如果那个格子中的宝贝价值比小明手中任意宝贝价值都大,小明就可以拿起它(当然,也可以不拿)。
当小明走到出口时,如果他手中的宝贝恰好是k件,则这些宝贝就可以送给小明。
请你帮小明算一算,在给定的局面下,他有多少种不同的行动方案能获得这k件宝贝。
思路:
定义状态dp[line][row][c][j] 为处于line行row列,当前取得宝物最大值为c,已拿j件宝物。
目前的状态由line+1状态,row+1状态,拿当前宝物状态的方法总数
状态转移方程可参看
if(line + 1 <= n){
if(c < array[line][row] && j < k) //在此处可拿这件宝物
{
dp[line][row][c][j] = (dp[line][row][c][j] + dfs(line + 1, row, array[line][row], j + 1)) % mod;
dp[line][row][c][j] = (dp[line][row][c][j] + dfs(line + 1, row, c, j)) % mod;
}
else{
dp[line][row][c][j] = (dp[line][row][c][j] + dfs(line + 1, row, c, j)) % mod;
}
}
if(row + 1 <= m){
if(c < array[line][row] && j < k) //在此处可拿这件宝物
{
dp[line][row][c][j] = (dp[line][row][c][j] + dfs(line, row + 1, array[line][row], j + 1)) % mod;
dp[line][row][c][j] = (dp[line][row][c][j] + dfs(line, row + 1, c, j)) % mod;
}
else{
dp[line][row][c][j] = (dp[line][row][c][j] + dfs(line, row + 1, c, j)) % mod;
}
}
完整代码如下:
#include
#include
using namespace std;
const int mod = 1000000007;
int n, m, k;
int array[51][51];
int dp[51][51][13][13]; //dp[i][j][c][k]:在第i行第j列当前宝物最大值为c已拿k件宝物
int dfs(int line, int row, int c, int j)
{
if(dp[line][row][c][j] != -1){
return dp[line][row][c][j];
}
if(line == n && row == m){
if(j == k){ //终点宝物不拿共有k件
return 1;
}
else if(j == k - 1 && c < array[line][row]){ //终点宝物拿过共有k件
return 1;
}
else{
return 0;
}
}
dp[line][row][c][j] = 0;
if(line + 1 <= n){
if(c < array[line][row] && j < k) //在此处可拿这件宝物
{
dp[line][row][c][j] = (dp[line][row][c][j] + dfs(line + 1, row, array[line][row], j + 1)) % mod;
dp[line][row][c][j] = (dp[line][row][c][j] + dfs(line + 1, row, c, j)) % mod;
}
else{
dp[line][row][c][j] = (dp[line][row][c][j] + dfs(line + 1, row, c, j)) % mod;
}
}
if(row + 1 <= m){
if(c < array[line][row] && j < k) //在此处可拿这件宝物
{
dp[line][row][c][j] = (dp[line][row][c][j] + dfs(line, row + 1, array[line][row], j + 1)) % mod;
dp[line][row][c][j] = (dp[line][row][c][j] + dfs(line, row + 1, c, j)) % mod;
}
else{
dp[line][row][c][j] = (dp[line][row][c][j] + dfs(line, row + 1, c, j)) % mod;
}
}
return dp[line][row][c][j];
}
int main()
{
int i, j;
cin >> n >> m >> k;
for(i=1; i<=n; i++)
for(j=1; j<=m; j++)
{
cin >> array[i][j];
}
memset(dp, -1, sizeof(dp));
cout << dfs(1, 1, -1, 0) % mod<< endl;
return 0;
}
uva10118 题目链接
四堆糖果,每堆n个糖果,糖果之间以不同的颜色区分,每次取出一个糖果放进篮子里,篮子最大容量为5,若篮子里两个糖果颜色相同,就可把这一对糖果放进口袋里,问最多可以放多少对糖果进口袋。
思路:
这题的状态以及状态转移比较复杂,定义状态为dp[a][b][c][d],表示第一堆拿a颗第二堆拿b颗第三堆拿c颗第四堆拿d颗的状态下能放进口袋的最大对数,用color[21]记录每个状态下各种颜色是否在篮子里(最多20种颜色),状态转移分为篮子里有颜色一样的和篮子里不同色来进行转移。
#include
#include
#include
using namespace std;
int array[41][5];
int top[5]; //每堆拿多少
bool color[21];
int dp[41][41][41][41]; //每一堆拿多少时的可以拿走的糖果最大对数
int n;
int dfs(int count)
{
int &ans = dp[top[0]][top[1]][top[2]][top[3]];
if(count == 5){ //篮子里不够装
return ans = 0;
}
if(ans != -1){
return ans;
}
ans = 0;
int i;
for(i=0; i<4; i++)
{
if(top[i] < n)
{
int c = array[top[i] + 1][i + 1];
top[i]++;
bool past = color[c];
if(color[c]){ //篮子里有同样颜色的
color[c] = false;
ans = max(ans, dfs(count - 1) + 1);
}
else{ //篮子里没有同样颜色的
color[c] = true;
ans = max(ans, dfs(count + 1));
}
//回溯,撤销对这堆的改变
color[c] = past;
top[i]--;
}
}
return ans;
}
int main()
{
int i, j, k;
while(1)
{
cin >> n;
if(n == 0){
break;
}
for(i=1; i<=n; i++)
for(j=1; j<=4; j++)
{
cin >> array[i][j];
}
memset(color, false, sizeof(color));
memset(top, 0, sizeof(top));
memset(dp, -1, sizeof(dp));
cout << dfs(0) << endl;
}
return 0;
}