题目大意:要煎一块有两个面的肉,只能在一段k不相交的时间段$[l_{i},r_{i}]$内翻转,求$2*n$秒后,保证两个面煎的时间一样长时,需要最少的翻转次数,$n<=100000$,$k<=100$
神仙单调队列优化$DP$, [NOI2005]瑰丽华尔兹 也有类似的压时间段的套路,但这道题可比那道题难多了。
朴素$O(n^2)$的$DP$没什么好说的,我们要想办法把它优化成$O(nk)$的
定义$f[i][j]$表示第$i$个时间段内,朝上的面(现在没被煎的)被煎的时间是$j$
1.观察翻转的过程,貌似在一个连续的时间段内翻转2次以上就是没有意义的 ,因为可以翻过去再翻回来
2.貌似并不一定要在整数时间翻转,但这种情况只在翻转1次的情况下有意义,所以整体把时间*2
然后,分情况讨论$DP$转移
1.翻0次,朝上的面被煎的时间不变,$f[i][j]=f[i-1][j]$,无需任何优化
2.翻2次,朝上的面被至多额外煎$r_{i}+l_{i}$秒,枚举上一次当前面被煎的时间$k$,可得$f[i][j]=min(f[i][k])+2\;(k<=j)$
对于这种情况,正序枚举$j$,单调队列优化$DP$即可,$j-k>r_{i}+l_{i}$的弹出队列
3.翻1次,原来朝上的面被翻到了下面,设现在的上面是$a$面,下面是$b$面,则$a$面被煎了$j$秒,$b$面被煎了$r_{i}-j$秒
那么上一次$a$面被煎的时间是$k$,此时$a$面朝下,朝上的面是$b$面,被煎的时间是$r_{i-1}-k$,可得$f[i][j]=min(f[i-1][r_{i}-k])+1$
因为是$-k$,要倒序枚举$j$,同样用单调队列优化,$k-j>r_{i}+l_{i}$弹出队列即可
虽然空间能开下$O(nk)$,但用滚动数组跑得飞快
#include <cstdio>
#include <cstring>
#include <algorithm>
#define N 205
#define M 401000
#define dd double
#define inf 0x3f3f3f3f
#define rint register int
using namespace std; int n,K,cnt;
int l[N],r[N],t[N];
int f[][M],que[M]; int main()
{
scanf("%d%d",&n,&K);
for(int i=;i<=K;i++){
scanf("%d%d",&l[i],&r[i]);
t[++cnt]=l[i]<<,t[++cnt]=r[i]<<;
}
memset(f,0x3f,sizeof(f));
f[][]=;int now=,pst=;
n<<=;
for(int i=;i<=cnt;i++)
{
if(i&) continue;
int hd=,tl=;
for(rint j=;j<=t[i];j++)
f[now][j]=inf;
for(rint j=;j<=t[i];j++)
{
f[now][j]=min(f[now][j],f[pst][j]);
while(hd<=tl&&f[pst][j]<=f[pst][que[tl]])
tl--;
que[++tl]=j;
while(hd<=tl&&j-que[hd]>t[i]-t[i-])
hd++;
f[now][j]=min(f[now][j],f[pst][que[hd]]+);
}
hd=,tl=;
for(rint j=t[i];j>=;j--)
{
while(hd<=tl&&f[pst][t[i]-j]<=f[pst][t[i]-que[tl]])
tl--;
que[++tl]=j;
while(hd<=tl&&que[hd]-j>t[i]-t[i-])
hd++;
f[now][j]=min(f[now][j],f[pst][t[i]-que[hd]]+);
}
swap(now,pst);
}
if(f[pst][n]==inf) printf("Hungry\n");
else printf("Full\n%d\n",f[pst][n]);
return ;
}