题目大意:有2*n的时间去煎一片肉,其中有k个不相交区间,在这些区间里可以翻面,求最后每面都煎n分钟的最小翻面次数。
有一个显而易见(exm???)的思路,设dp状态为dp[i][j]表示在第i分钟朝上的面煎了j分钟,
那么转移就是dp[i][j]=min(dp[i-1][j],dp[i-1][i-j]+1),就是上一秒不翻,或者上一秒翻了,并且之前向上的时间为j,现在就是i-j。
但是这显然超时,我们发现k≤100,可以把时间转换为区间,而且我们发现,对于一个区间,只有三种情况:
1.不翻转,直接继承dp[i][j]=dp[i-1][j]。
2.翻转一次,设当前朝上的面的时间为j,那么现在朝下的面的时间为r-j,设这段时间里现在朝下的面烤了k分钟,那么之前这面
就烤了r-j-k分钟,而这面就是翻面前的上面,所以dp[i][j]=dp[i-1][r-j-k]+1。
3.翻转两次,设当前朝上的面时间为j,这段时间烤的时间为k,那么之前烤的就是j-k分钟。所以dp[i][j]=dp[i-1][j-k]+2。
但是这仍然很慢,所以我们考虑队列优化。
对于翻转两次的情况,
设
所以选择大于等于当前枚举时间+l-r的值作为更新,维护递增序列
对于翻转一次的情况,也是一样,不过因为条件是所以为了更新,我们倒叙枚举,推入队列的为r-j。
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
using namespace std;
int n,k;
int dp[105][100005];
int que[100005];
int main()
{
scanf("%d%d",&n,&k);
memset(dp,0x3f,sizeof(dp));dp[0][0]=0;
for(int w=1;w<=k;w++)
{
int l,r;
scanf("%d%d",&l,&r);
int head=1,tail=0;
memcpy(dp[w],dp[w-1],sizeof(dp[w]));
for(int i=0;i<=min(r,n);i++)
{
while(head<=tail&&dp[w-1][que[tail]]>=dp[w-1][i])tail--;
while(head<=tail&&que[head]<i+l-r)head++;
que[++tail]=i;
dp[w][i]=min(dp[w][i],dp[w-1][que[head]]+2);
}
head=1,tail=0;
for(int i=r;i>=max(r-n,0);i--)
{
while(head<=tail&&dp[w-1][que[tail]]>=dp[w-1][r-i])tail--;
while(head<=tail&&que[head]<l-i)head++;
que[++tail]=r-i;
dp[w][i]=min(dp[w][i],dp[w-1][que[head]]+1);
}
}
if(dp[k][n]==0x3f3f3f3f)
{
printf("Hungry");
return 0;
}
printf("Full\n");
printf("%d",dp[k][n]);
return 0;
}