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
#include
#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]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]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]);
}

你可能感兴趣的:(DP)