Codeforces 939F.Cutlet-单调队列优化dp

传送门

题意:

有2∗n的时间去煎一块两面的肉,给你k个可以翻转的区间 [li,ri] [ l i , r i ] ,可以在区间内翻转任意次, 保证区间不相交
问是否存在合法的方案使得两面恰好都只煎了 n 分钟,并求最小翻转次数
n<=100000,k<=100

Solution:

考虑朴素的dp

f[i][j] f [ i ] [ j ] 表示前i秒,当前不在烤的面被烤过j秒,所需的最短翻转次数

转移 f[i][j]=f[i1][j],f[i][j]=f[i1][ij]+1 f [ i ] [ j ] = f [ i − 1 ] [ j ] , f [ i ] [ j ] = f [ i − 1 ] [ i − j ] + 1

但是这样是 O(n2) O ( n 2 ) 的,需要优化

注意到第二个转移只在这k个区间里才会发生

那么我们可以转变一下状态:

f[i][j] f [ i ] [ j ] 表示前 ri r i 秒,当前不在烤的面被烤过j秒,所需的最短翻转次数

同时我们还发现每个区间肉最多被翻转2次,那么我们就可以分成翻一次和翻两次来讨论了

转移即为
f[i][j]=minkrilik=0(f[i1][jk])+2 f [ i ] [ j ] = m i n k = 0 k ≤ r i − l i ( f [ i − 1 ] [ j − k ] ) + 2 , f[i][j]=minkrilik=0(f[i1][rijk])+1 f [ i ] [ j ] = m i n k = 0 k ≤ r i − l i ( f [ i − 1 ] [ r i − j − k ] ) + 1

发现这两个转移分别都是满足单调性的,使用单调队列优化即可

代码:

#include<cstdio>
#include<iostream>
#define debug(x) l=l
using namespace std;
const int inf=1e9;
int n,m;
int f[110][100010];
int q[100010],h,t,l,r;
int main()
{
    scanf("%d%d",&n,&m);
    for (int i=1;i<=n;i++) f[0][i]=1e9;
    f[0][0]=0;
    for (int i=1;i<=m;i++)
    {
        scanf("%d%d",&l,&r);
        h=1,t=0;
        for (int j=0;j<=n;j++) f[i][j]=f[i-1][j];
        for (int j=0;j<=r;j++)
        {
            debug(j);
            if (j<=n)
            {
                while (h<=t&&f[i-1][j]<=f[i-1][q[t]])t--;
                q[++t]=j;
            }
            while (h<=t&&q[h]<j-(r-l)) h++; 
            if (h<=t) debug(q[h]),debug(f[i][j]),f[i][j]=min(f[i][j],f[i-1][q[h]]+2),debug(f[i][j]);
        }
        h=1,t=0;
        for (int j=r;j>=0;j--)
        {
            debug(j); 
            if (r-j<=n)
            {
                while (h<=t&&f[i-1][r-j]<=f[i-1][q[t]])t--;
                q[++t]=r-j;
            }
            while (h<=t&&q[h]<l-j) h++; 
            if (h<=t) debug(q[h]),debug(f[i][j]),f[i][j]=min(f[i][j],f[i-1][q[h]]+1),debug(f[i][j]);
        }
    }
    if (f[m][n]>=inf) printf("Hungry");
    else printf("Full\n%d",f[m][n]);
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值