[SMOJ1781]恐怖电影

97 篇文章 0 订阅
15 篇文章 0 订阅

题目描述

John 有 N 部恐怖片子,编号 0 至 N1。第 i 部恐怖片的播放总长度是 Lengthi 分钟。现在 John 很累了, 所以他可能在看某部电影过程中睡着了。唯一能让他一直保持不睡着的条件是:他受到的惊吓程度 Level 始终不低于某个值。一开始 ,John 没看任何恐怖片之前的 Level 是定值 74。 他的 Level 随着时间的流逝而递减,每过一分钟就减少 1。 例如过了 6 秒后,那么 Level 值就减少 0.1。一旦 Level 值小于 -0.5, 那么 John 就睡着了。 每个恐怖片只有一个恐怖镜头,一旦 John 看到了这个恐怖镜头,他的 Level 值马上增加 47。第 i 个恐怖片的恐怖镜头在该片子的第 scaryi 分钟出现。

现在 John 想看完尽量多的恐怖片,他让你帮它安排恐怖片的编号的排列,就是说,你要决定一个 0 至 N1 的排列,按照这个顺序,John 就可以在睡着前能看完尽量多的恐怖片。如果有多种方案可以看完相同多的恐怖片,输出字典序最小的序列。注意:一个恐怖片只有被 John 从头看到尾,才算看完, 一旦 John 在看这个恐怖片过程中睡着了,那么他就没办法看完这部恐怖片,剩下还没看的哪些恐怖片也无法再看到了。注意:电影是连续播放的,即一个电影完了后,瞬间就播放下一个电影。

输入格式 1781.in

第一行:一个整数, N 。 表示恐怖片的数量。1N20
第二行: N 个整数,第 i 个整数表示 Lengthi 2Lengthi474 , 对于 0iN1
第三行: N 个整数,第 i 个整数表示 scaryi 1scaryi<Lengthi

输出格式 1781.out

能尽量多的看完恐怖片的情况下,字典序最小的恐怖片的编号序列。

输入输出样例 1781.in 1781.out


这题我自认在比赛的时候做得不够好,本应该能够在赛场上编出来的。但是至少想到了状压 DP,只是思考的方向错了。

我考试时候想的是, f[state][i] 为看完 state 这些电影且最后一部为 i 能获得的最大 level 值。但是这样一来也不知道数量,也不知道顺序,搞得我一头雾水,调了半天重写还是感觉不对。其实我还是想多了,直接 DP 最多能看多少部电影就行了, level 值只是判断的时候计算一下而已,不用作为记录的值。

所以还是那句话,对于一些看起来比较麻烦的问题,稍加转化就可变成简单明了的 DP 问题。把问题剥丝抽茧,通过分析主要矛盾,决定一下,问题的状态应该如何描述更简便?以及状态的不同表示方式,都会决定问题规划和实现的难度,影响着算法的效率。
由此可见,DP 问题中具体如何规划,会直接决定我们解决问题的难易程度、算法在时间和空间上的复杂度。除此之外,注意在具体的规划过程中的灵活性和技巧性将是 DP 方法提出的更高要求。

继续回来讲到这题,我们不妨用 fstate 表示在 state 这些电影中,恰当地安排顺序,能够看完的最多电影数。也就是说我们只知道看完多少部,但是对于具体的顺序是不清楚的。比如 f[{1, 2}]=2,意味着在安排得当的情况下,可以把第 1、2 两部电影都看完。但是要注意,不能任意安排!

暂时先不考虑顺序的问题吧,再加一个辅助状态 levstate 表示尽可能多地看 state 这些电影能够获得的最大 level 值,那么状态转移方程就可以写成

fstate=max{fstate{i}+1} if istate and levstatescaryi and levstate+47Lengthi

但是仅仅是这样还不够,因为我们无法解决顺序的问题。其实也很好办,再加一个辅助的 lastWatchstate ,表示在尽可能多看的情况下,最后看的是哪一部。最后转移的时候,如果有多种方案,我们就取 lastWatch[] 最大的一种。(字典序小,自然后面的尽量大)然后就可以得到观看顺序了。

当然,虽然直接实现 DP 也可以,但是如果写成记忆化搜索的形式更为方便,我们只需要加一个 level 参数就可以计算了,省下了一个数组。但是 lastWatch 应该要变成 firstWatch ,因为搜索和 DP 的顺序是相反的。

参考代码:

#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>

using namespace std;

const int maxn = 21;

int N;
int Length[maxn], scary[maxn];

int dp[1 << maxn];
int firstWatch[1 << maxn];
bool watch[maxn];

void solve(int state, int level) {
    if (dp[state] != -1 || !state) return; //边界

    for (int i = 0; i < N; i++)
        if (((1 << i) & state) && level >= scary[i] && level + 47 >= Length[i]) {
            int subTask = state - (1 << i); //子问题
            solve(subTask, level - Length[i] + 47);
            if (dp[state] < dp[subTask] + 1) { //可以更新
                dp[state] = dp[subTask] + 1;
                firstWatch[state] = i;
            }
        }
}

int main(void) {
    freopen("1781.in", "r", stdin);
    freopen("1781.out", "w", stdout);

    scanf("%d", &N);
    for (int i = 0; i < N; i++) scanf("%d", &Length[i]);
    for (int i = 0; i < N; i++) scanf("%d", &scary[i]);

    int upperLim = 1 << N;
    memset(dp, -1, sizeof dp);
    memset(firstWatch, 0, sizeof firstWatch);
    solve(upperLim - 1, 74);

    memset(watch, false, sizeof watch);
    for (int i = upperLim - 1; i >= 0;) {
        if (dp[i] == -1) break;
        printf("%d ", firstWatch[i]);
        watch[firstWatch[i]] = true;
        i -= (1 << firstWatch[i]);
    }

    for (int i = 0; i < N; i++) if (!watch[i]) printf("%d ", i);
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值