P6064 [USACO05JAN]Naptime G
阅读原文时间:2023年07月09日阅读:1

最近做了多少道 usaco 了,连 FJ 都认识我了呀

题意描述

传送门

给你 \(N\) 段时间其中 \(B\) 段时间你要用来睡眠,再给你每个时间睡眠可获得的效用值 \(U_i\)。

可惜的是你每次睡眠的第一段时间都要用来入睡(安眠药它不香吗)。

求你可以获得的最大效用值。

算法分析

一眼看上去就是 DP 啊。

定义 \(f(i,j,1/0)\) 表示:在第 \(i\) 段时间,已近休息了 \(j\) 段时间,此时是否休息。

假设没有循环时间,那么状态转移方程为:

\(f(i,j,1)=max(f(i-1,j-1,0),f(i-1,j-1,1)+u_i)\)

\(f(i,j,0)=max(f(i-1,j,0),f(i-1,j,1))\)

\(f(i,0,0)=f(1,1,1)=0(1\leq i\leq n),f(i,j,0/1)=-INF(2\leq i,j\leq n)\)

那么如果没有时间阶段是不断循环的圆这句话这道题就没了。

那么有的话怎么办办呢?(暗杀良心出题人同志并把这句话删了)

我的思路是枚举每一段时间为起点,做一次 DP,默默算算时间(\(O(n^3)\))后自闭。

后来老师说其实只需要两次 DP:

  1. 正常的当时间循环不存在的 DP。
  2. 保证最后一段时间入睡,第一段时间睡着的 DP。(否则时间循环将没有任何意义)

然后就没了。

代码实现

#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<iostream>
#define N 3831
using namespace std;

int n,m,a[N],f[N][N][2],ans=0;

int read(){
    int x=0,f=1;char c=getchar();
    while(c<'0' || c>'9') f=(c=='-')?-1:1,c=getchar();
    while(c>='0' && c<='9') x=x*10+c-48,c=getchar();
    return x*f;
}

int main(){
    n=read(),m=read();
    for(int i=1;i<=n;i++) a[i]=read();
    memset(f,0xcf,sizeof(f));
    f[1][1][1]=0;
    for(int i=1;i<=n;i++) f[i][0][0]=0;
    for(int i=2;i<=n;i++)
        for(int j=1;j<=m;j++){
            f[i][j][0]=max(f[i-1][j][0],f[i-1][j][1]);
            f[i][j][1]=max(f[i-1][j-1][0],f[i-1][j-1][1]+a[i]);
        }
    ans=max(f[n][m][0],f[n][m][1]);
    memset(f,0xcf,sizeof(f));
    f[1][1][1]=a[1];
    for(int i=1;i<=n;i++) f[i][0][0]=0;
    for(int i=2;i<=n;i++)
        for(int j=1;j<=m;j++){
            f[i][j][0]=max(f[i-1][j][0],f[i-1][j][1]);
            f[i][j][1]=max(f[i-1][j-1][0],f[i-1][j-1][1]+a[i]);
        }
    ans=max(ans,f[n][m][1]);
    printf("%d\n",ans);
    return 0;
}

完结撒花。

手机扫一扫

移动阅读更方便

阿里云服务器
腾讯云服务器
七牛云服务器