记忆化搜索题目总结(2)

记忆化搜索题目总结(2)

在上一篇我们对记忆化搜索的概念含义做了介绍,并举了几个基础了例子,没看过上一篇的请点击记忆化搜索题目总结(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<iostream>
#include<cstring>
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<iostream>
#include<cstring>
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;
}

三. Free Candies

uva10118 题目链接
四堆糖果,每堆n个糖果,糖果之间以不同的颜色区分,每次取出一个糖果放进篮子里,篮子最大容量为5,若篮子里两个糖果颜色相同,就可把这一对糖果放进口袋里,问最多可以放多少对糖果进口袋。

思路:
这题的状态以及状态转移比较复杂,定义状态为dp[a][b][c][d],表示第一堆拿a颗第二堆拿b颗第三堆拿c颗第四堆拿d颗的状态下能放进口袋的最大对数,用color[21]记录每个状态下各种颜色是否在篮子里(最多20种颜色),状态转移分为篮子里有颜色一样的和篮子里不同色来进行转移。

#include<iostream>
#include<algorithm>
#include<cstring>
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;
 } 
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值