洛谷 P4852 yyf hates choukapai【dp】【单调队列】


题目:

洛谷传送门


题意:

每张卡片都有一定的幸运值,现在一共有 n ∗ c + m n*c+m nc+m张牌,要求必须连抽 c c c次,每次 n n n张牌,而单抽不能连续 d d d次,问最大的幸运值是多少


分析:

对于连抽,我们可以理解成:在第 i i i位开始抽,对答案的贡献只有 a i a_i ai。换而言之,当我们选 i i i作为连抽起点时,我们失去的幸运值为 ∑ j = i + 1 i + c − 1 a j \sum_{j=i+1}^{i+c-1}a_j j=i+1i+c1aj。所以我们可以设 b i b_i bi表示以 i i i为起点时我们失去的幸运值
这样一来,题目就转化为在 n ∗ c + m n*c+m nc+m个位置中选择 n n n个,使得 b b b的总和最小
我们设 f i , j f_{i,j} fi,j表示在 1 − i 1-i 1i的位置里,选择了 j j j个起点且 i i i也一定选择了
所以 f i , j = f k , j − 1 + b i ( k ∈ ( i − c − d , i − c ) ) f_{i,j}=f_{k,j-1}+b_i(k\in(i-c-d,i-c)) fi,j=fk,j1+bi(k(icd,ic))
但因为这样的时间复杂度实在承受不起,于是我们考虑下如何优化 d p dp dp
然后就有了单调队列优化,因为起点越靠后而失去的幸运值是一样的,那么显然是靠后的更优


代码:

// luogu-judger-enable-o2
#include<cstdio>
#include<string>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<queue>
#define LL long long 
#define LZX ILU
using namespace std;
inline LL read() {
    LL d=0,f=1;char s=getchar();
    while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
    while(s>='0'&&s<='9'){d=d*10+s-'0';s=getchar();}
    return d*f;
}
int x[200005],cnt[200005],p[200005],f[200005][45];
struct mo{
    int w,i;
}q[200005][45];
int fa[200005][45],h[45],t[45];
void work(int k,int king)
{
    if(!k) return;
    work(fa[k][king],king-1);
    printf("%d ",k);
    return;
}
int main()
{
    int n=read(),m=read(),c=read(),d=read();
    int s=c*n+m;
    for(int i=1;i<=s;i++) x[i]=read(),cnt[i]=cnt[i-1]+x[i];
    for(int i=1;i<=s-c+1;i++) p[i]=cnt[i+c-1]-cnt[i];
    int k,ans=0;
    for(int i=1;i<=n;i++) h[i]=1;
    for(int i=1;i<=s-c+1;i++)
    {
    	if(i<=c) {f[i][1]=p[i];continue;}
        for(int j=(i+c-2)/(c+d)+1;j<=n&&j<=(i+c-1)/c;j++)
        {
            if(h[j-1]<=t[j-1]&&q[h[j-1]][j-1].i<i-c-d) h[j-1]++;
            if(j-1>=(i-2)/(c+d)+1&&j-1<=(i-1)/c)
            {
                while(h[j-1]<=t[j-1]&&q[t[j-1]][j-1].w>=f[i-c][j-1]) t[j-1]--;
                q[++t[j-1]][j-1].w=f[i-c][j-1];
                q[t[j-1]][j-1].i=i-c;
            }
            f[i][j]=q[h[j-1]][j-1].w+p[i];
            fa[i][j]=q[h[j-1]][j-1].i;
            if(i>=s-c-d+1&&j==n&&ans<cnt[s]-f[i][j]) ans=cnt[s]-f[i][j],k=i;
        }
    }
    printf("%d\n",ans);
    work(k,n);
    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值