好题。
我的思考方向:一段不能翻的区间,一定是烤同一方向。可以把原来的那个东西变成一堆类似
1 1 1 1 1 20 1 1 1 1 15 1 11 ...
的区间,求在其中取尽量少的区间个数,使得其和为n
。。然而个数依然是\(10^5\)个,没有实质性的突破。
然后就不会了
考虑朴素算法。可以想到f(i,j)为前i的时间正面烤了i的单位时间的翻转次数。
这样是从\(f(..,j-1)\to f(...,j)\)的。
这样太麻烦了,不妨设为背面烤了i。
那么转移会变简单。
我们发现这样转移,在没有不能执行操作的区间是直接赋值赋过去的。所以可以考虑变为\(f(i,j)\)为前i个可操作区间,背面时间为j。
可以发现一个可操作区间只会操作02次。多的几次操作都可以转化为02次且更优。
所以可以枚举一个当前区间给背面烤了k的时间,发现转移取min是有单调性的,可用单调队列。
#include<bits/stdc++.h>
using namespace std;
#define orz cout<<"lytcltcltcltcltcltcl"<<endl
inline int r(){int s=0,k=1;char c=getchar();while(!isdigit(c)){if(c=='-')k=-1;c=getchar();}while(isdigit(c)){s=s*10+c-'0';c=getchar();}return s*k;}
int n,k,f[105][100005];
deque<int>q;
deque<int>bh;
struct node
{
int l,r;
}a[1000001];
bool cmp(node x,node y)
{
return x.l<y.l;
}
int main()
{
n=r();k=r();
for(int i=1;i<=k;i++)
{
a[i].l=r();a[i].r=r();
}
memset(f,0x3f,sizeof(f));
f[0][0]=0;
sort(a+1,a+k+1,cmp);
for(int i=1;i<=k;i++)//k段
{
for(int j=0;j<=n;j++)f[i][j]=f[i-1][j];//不翻
while(!q.empty())q.pop_front(),bh.pop_front();
for(int j=a[i].r;j>=0;j--)//背面烤了j s
{
while(!bh.empty()&&bh.front()<a[i].l-j)q.pop_front(),bh.pop_front();
while(!bh.empty()&&q.back()>=f[i-1][a[i].r-j])q.pop_back(),bh.pop_back();
q.push_back(f[i-1][a[i].r-j]);
bh.push_back(a[i].r-j);
f[i][j]=min(f[i][j],q.front()+1);
}
while(!q.empty())q.pop_front(),bh.pop_front();
for(int j=0;j<=min(n,a[i].r);j++)
{
while(!bh.empty()&&bh.front()<j-a[i].r+a[i].l)q.pop_front(),bh.pop_front();
while(!bh.empty()&&q.back()>=f[i-1][j])q.pop_back(),bh.pop_back();
q.push_back(f[i-1][j]);
bh.push_back(j);
f[i][j]=min(f[i][j],q.front()+2);
}
}
if(f[k][n]<1e9)cout<<"Full\n"<<f[k][n];
else cout<<"Hungry";
}