NKOJ-Unknow 不死的 LYM

10 篇文章 1 订阅
3 篇文章 0 订阅

不死的 LYM
问题描述

子曰: 不睡觉就会死。
深信此话的 LYM 决定在本学期接下来的 n 节课上考虑一下睡觉的问题。 LYM
认为如果在一堂课上睡觉,身体的疲劳值就会下降,反之如果在一堂课上不睡觉,
身体的疲劳值就会上升。而身体对疲劳的忍耐是有限度的,一旦疲劳值超过限度,
LYM 就会 go die,于是他不得不在一些课上睡觉。 注意, LYM 的疲劳值只会在一
节课上完后发生改变, 如果上完最后一节课后, 疲劳值超出了限度, LYM 仍然会
go die。
不过, 在不同的课上, 疲劳值的变化量并不总是一样, 就如在班主任的课上
睡觉, 疲劳值并不会下降太多, 因为那样会睡得很不安心。
LYM 是一个死要面子的人, 他宁可冒着生命危险, 也要挽回自己在老师心中
的形象, 因此他不能总是在人家的课上睡觉。 他给自己定下了一个规矩: 决不连
续地在同一主科的课上睡觉, 即如果 LYM 在主科 X 的某堂课上睡了觉, 那么在下
一堂(不一定是相邻的)主科 X 的课上,LYM 就绝不会睡觉。
经过了这 n 节课后, LYM 竟然没有死, LYM 想知道自己对疲劳值的忍耐极限
至少是多少?

输入文件

从文件 survive.in 中读入数据。
第一行, 一个正整数 n.
第二行, n 个正整数, 表示这 n 节课的课程安排。 每个整数代表一门课程,
科目代号对应关系参见下文的表格。 (1~6 号学科均为主科,7 号学科不算作主
科).
第三行, n 个正整数, 其中第 i 个数表示在第 i 节课上睡觉, 疲劳值的减少
量。
第四行, n 个正整数, 其中第 i 个数表示在第 i 节课上不睡觉, 疲劳值的增
加量。
第五行, 一个整数, 表示 LYM 的初始疲劳值。 如果初始疲劳值大于了忍耐限
度, LYM 会在第一节课前就暴亡。
科目语文数学英语物理化学信竞其他
代号123456

输出文件

一个整数, 表示 LYM 的忍耐限度的最小可能值。重庆南开

输入输出样例

样例输入 1

5 
7 4 4 5 4
1 6 6 4 4
6 3 8 7 7
8

样例输出 1

9

样例说明 1

第 1, 2, 4, 5 节课睡觉;
第 3 节课不睡觉.

样例输入 2

3 
6 6 7
3 4 5
5 4 3
-1

样例输出 2

0

样例说明 2

第 1, 3 节课睡觉;
第 2 节课不睡觉.

数据规模

对于 20%的数据, 有且仅有一门主科。
对于 100%的数据, 1<=n<=5000,第三, 四行的数据保证大于等于 0.
保证所有输入和输出都在[-2000000000,2000000000]范围内.
没有说明是正整数的数据不保证为正。

有规划地上课睡觉 这很重要

题解

分析题目

显然是动态规划

首先 它求的是一天的所有疲劳值中最大的一个
其次 它求的是所有可能性的最大值中 最小的一个(这句话不是很重要)

它有一个限制条件就是 相同的科目不会连续睡两节课
这就很尴尬了 我们需要记录每一节课之后他睡觉的情况
即 当前的状态要有六个值表示->上一次上每一节课睡觉了没有

六个值是因为第七节课可以连续睡觉
那可以连续睡 肯定选择不管不顾地睡觉啊 还用想么

记录状态+状态的个数为6个=状态压缩 (非常漂亮的一个等式)

于是 开一个六位的二进制数(范围在0-63)记录当前的状态

状态f[i][j]表示第i节课的状态为j时的疲劳值

又由于它求的是一天的所有疲劳值中最大的一个

状态fmax[i][j]记录第i节课为止时 状态为j的前i节课的最大疲劳值

状态转移

第i节课睡觉恢复疲劳值为rest[i] 上课增加疲劳值为 study[i]

对于这节课为k

如果上节课的状态二进制j的第k位为1 则表示上节k已经睡过了 
那么这节课就不能再睡了 这节课的状态第k为就要为0

    f[i][j^(1<<k-1)]=min(f[i][j^(1<<k-1)]f[i-1][j]+study[i])

反之 则有两种选择 这节课继续学习和选择睡觉

继续学习
    f[i][j]=min(f[i][j],f[i-1][j]+study[i])
睡觉
    f[i][j^(1<<k-1)]=min(f[i][j^(1<<k-1)],f[i-1][j]+rest[i])

如果k为第七节课
    f[i][j]=f[i-1][j]+rest[i]

对于fmax的更新

每次状态转移后
    fmax[i][j]=max(f[i][j],fmax[i][j])

二分

为什么要二分呢??

举个例子
对于i 和 i+1
rest[i]=1       work[i]=10
rest[i+1]=1000  work[i]=1
且当前f[i-1][j]=fmax[i-1][j]

那么就有两种情况
    第i天休息 第i-1天上课 那么疲劳值f[i+1]=f[i-1]     上限fmax[i+1]=fmax[i-1]
    第i天上课 第i-1天休息 那么疲劳值f[i+1]=f[i-1]-990 上限fmax[i+1]=fmax[i-1]+1
如果选择第一种情况 那么后面的f就很有可能会超出fmax
而选择第二种情况 后面的f超出fmax的几率更小 但是fmax在f[i]就被拉高了

这就表明 这个涉及到两个值的DP后效性极强 想要通过DP根治是没法了
所以我们选择二分fmax的上限来达到求结果的目的

这样就结束了呀

思路

动态规划 这个一看就感觉是动归 没什么思路可言
一般没法用一个简单的数字表示状态时 就用状压 用不了再想其它的
对于无法根治的后效性 我们就一般选用二分法

这道题目比较重要的细节在于 对于fmax的强后效性的感觉
一般这种东西都要三思而后觉 所以一般都要靠感觉…

附上对拍代码

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <queue>
#include <cstring>
using namespace std;

inline long long input()
{
    char c=getchar();long long o;bool f=0;
    while(c>57||c<48)f|=(c=='-'),c=getchar();
    for(o=0;c>47&&c<58;c=getchar())o=(o<<1)+(o<<3)+c-48;
    return f?-o:o;
}

long long n,les[5234],rest[5234],stu[5234];
long long res,f[5234][67],fres[5234][67];
bool mark[5234][67];

bool DP(long long maxr)
{
    memset(mark,0,sizeof(mark));
    mark[0][0]=1;res=2123456789;
    long long nslep,nres;bool flag=0;
    for(int i=1,l=les[i];i<=n;i++,l=les[i])
    for(int slep=0;slep<64;slep++)
    if(mark[i-1][slep])
    if(l<6)
    {
        nslep=slep^(1<<l);
        nres=f[i-1][slep]+((slep&(1<<l))?stu[i]:rest[i]);
        if(nres>maxr)continue;
        if(!mark[i][nslep]||(mark[i][nslep]&&f[i][nslep]>nres))
        {
            mark[i][nslep]=1;
            f[i][nslep]=nres;
            fres[i][nslep]=max(fres[i-1][slep],nres);
        }
        //
        if(!(slep&(1<<l)))
        {
            nres=f[i-1][slep]+stu[i];
            if(nres>maxr)continue;
            if(!mark[i][slep]||(mark[i][slep]&&f[i][slep]>nres))
            {
                mark[i][slep]=1;
                f[i][slep]=nres;
                fres[i][slep]=max(fres[i-1][slep],nres);
            }
        }
    }
    else
    {
        fres[i][slep]=fres[i-1][slep];
        if(mark[i][slep])f[i][slep]=min(f[i][slep],f[i-1][slep]+rest[i]);
        else
        {
            mark[i][slep]=1;
            f[i][slep]=f[i-1][slep]+rest[i];
        }
    }
    for(int slep=0;slep<64;slep++)
    if(mark[n][slep])flag=1,res=min(res,fres[n][slep]);
    return flag;
}

int main()
{
    freopen("survive.in","r",stdin);
    freopen("survive.out","w",stdout);
    n=input();res=2123456789;
    for(int i=1;i<=n;i++)les[i]=input()-1;
    for(int i=1;i<=n;i++)rest[i]=-input();
    for(int i=1;i<=n;i++)stu[i]=input();
    mark[0][0]=1;fres[0][0]=f[0][0]=input();
    //
    int l=f[0][0],r=2000000000,mid;
    while(l<r)
    {
        mid=l+r>>1;
        if(DP(mid))r=res;
        else l=mid+1;
    }
    printf("%d",r);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值