Luogu P9180 [COCI2022-2023#5] Slastičarnica 题解 [ 蓝 ] [ 区间 dp ] [ dp 状态优化 ] [ 前缀和优化 ]

Slastičarnica:非常好的区间 dp 题。

暴力

不难设计出暴力状态:dpq,i,j 表示进行到第 q 次操作,剩下区间 [i,j] 是否可行。

直到全部状态都为 0 的时候输出即可。

期望得分 19pts

优化(假)

观察到每个 dp 状态都只有两个值 0,1,那么我们可以考虑将状态中的某一维放进 dp 值里。

首先尝试把 q 换进去,状态设计为 dpi,j 为剩余区间 [i,j] 时进行的最大操作数为 q

那么我们怎么转移?从大往小枚举区间,然后根据当前进行的操作,每次查询满足要求的前缀后缀,转移一下即可。

此时我们不难发现,q 的最大值只能是 n,根本无法取到更大。这就是这题的诈骗点。

时间复杂度是 O(n3) 的,而我们可以发现无法继续优化下去(四边形不等式等都用不了),就要换一种思路。

贪心

显然,对于一段区间而言,长度越长,自然是对答案更优。为什么?因为每次操作都可以留出更多的空间来操作。

于是我们考虑固定左端点,把右端点换出来。

正解

设计状态 dpq,i 表示进行到了第 q 次操作,左端点为 i 时的最大右端点在哪。

每次操作可以选前缀、后缀,于是我们分两种转移。

前缀

dpq,i 能转移当且仅当 [id,i1] 能被消掉,然后转移左端点在 i 前面的 j 的所有 dpq1,j 的值。

这个可以做一个很显然的前缀和优化,不再细说。

后缀

感觉这个比较难搞。

dpq,i 能转移的条件是在 [j,dpq1,j] 中有能消除的后缀,同时在这些后缀中取最大的左端点转移。

观察到 dpq1,j 也在 [1,n] 之间,于是我们可以预处理出对于每一个点,在他左边的最大可消除后缀的左端点在哪,这个可以 O(n) 处理出。

然后每次转移一下即可。

总体时间复杂度 O(min(n,q)n)

细节

特判整个序列被删完的情况。只需要在删后缀里面加特判即可。因为要删完肯定是一次删全部,那么此时就没有前缀后缀之分了,算哪个都可以。

代码

#include <bits/stdc++.h>
#define fi first
#define se second
#define lc (p<<1)
#define rc ((p<<1)|1)
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pi;
int n,q,a[5005],mn[5005][5005],dp[5005][5005],rmx[5005];
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    cin>>n>>q;
    for(int i=1;i<=n;i++)cin>>a[i];
    memset(mn,0x3f,sizeof(mn));
    for(int i=1;i<=n;i++)
    {
        mn[i][i]=a[i];
        for(int j=i+1;j<=n;j++)mn[i][j]=min(mn[i][j-1],a[j]);
    }
    memset(dp,-1,sizeof(dp));
    dp[0][1]=n;
    for(int i=1;i<=min(n,q);i++)
    {
        bool ed=1;
        int d,s,pre=-1,p=-1;
        cin>>d>>s;
        for(int j=1;j<=n;j++)
        {
            if(j-d>=1)pre=max(pre,dp[i-1][j-d]);
            if(j-d>=1&&mn[j-d][j-1]>=s)dp[i][j]=max(dp[i][j],pre);
        }
        for(int j=1;j<=n;j++)
        {
            if(j-d+1>=1&&mn[j-d+1][j]>=s)p=max(p,j-d+1);
            rmx[j]=p;
        }
        for(int j=1;j<=n;j++)
        {
            if(rmx[dp[i-1][j]]-1>=j)dp[i][j]=max(dp[i][j],rmx[dp[i-1][j]]-1);
            if(dp[i][j]<j)dp[i][j]=-1;
            if(rmx[dp[i-1][j]]==j)ed=0;
        }
        for(int j=1;j<=n;j++)if(dp[i][j]!=-1)ed=0;
        if(ed)
        {
            cout<<i-1;
            return 0;
        }
    }
    cout<<q;
    return 0;
}
posted @   KS_Fszha  阅读(12)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
点击右上角即可分享
微信分享提示