CF939F Cutlet 题解
CF939F Cutlet 题解
看了一眼竟没有思路,DP 还得再练啊。
首先容易发现正反面是对称的,所以可以钦定当前烤的永远是正面,然后可以设 \(f_{i,j}\) 表示烤到第 \(i\) 分钟、反面烤了 \(j\) 分钟的最小翻转次数。则有转移方程:
显然,这是 \(O(n^2)\) 的,并没有什么前途。
注意到 \(k\) 很小,可以容纳一个 \(O(nk)\) 的算法。显然上面的那个转移方程中不在 \([l,r]\) 范围内的都是没有用的,所以修改状态设计为:设 \(f_{i,j}\) 表示对于前 \(i\) 个区间,到了 \(r_i\) 时间,反面烤了 \(j\) 分钟的最小翻转次数。
但是这样的状态设计使得我们的翻转需要面向区间来考虑。发现一个区间内最多翻转两次,下面分类讨论:
-
只翻转一次,那么枚举一个时间 \(k\),代表翻转过后正面多烤了 \(k\) 分钟,那么有 \(0\le k\le r-l\)。由状态定义得,正面在本区间烤了 \(r-j\) 分钟;又因为翻转之后正面多烤了 \(k\) 分钟,那么翻转过后的那一刻正面烤了 \(r-j-k\) 分钟。这个正面在翻转前是背面,所以这种情况的转移方程就是
\[f_{i,j}=\min\{f_{i,r-j-k}+1\}~. \]因为 \(k\le r-l\),所以得出 \(r-j-k\ge l-j\),也就是说这个最优决策点在 \([l-j,r-j]\) 这个范围内,因此用单调队列维护这个最优决策点。
-
翻转两次,同理枚举一个时间 \(k\) 表示翻转之后正面多烤了 \(k\) 分钟,同样有 \(k\le r-l\)。因为翻转两次等价于将当前的反面翻到正面烤了 \(k\) 分钟再翻回来,于是有转移方程
\[f_{i,j}=\min\{f_{i-1,j-k}+2\}~. \]同样由 \(k\le r-l\) 得出 \(j-k\ge j-r+l\),也满足位于一个 \([j-r+l,j]\),同样用单调队列维护。
需要注意的是,翻转一次的情况中随着 \(j\) 的增大,决策区间是向左移的,所以 \(j\) 要倒推;而在翻转两次的情况中,\(j\) 增大时决策区间是正常地往右移,所以顺推。
另外,这个转移方程中只用到了 \(i\) 和 \(i-1\),所以需要用滚动数组优化空间。
#include<bits/stdc++.h>
using namespace std;
constexpr int MAXN=2e5+5;
int n,k,q[MAXN],f[2][MAXN];
int main(){
cin.tie(nullptr)->sync_with_stdio(0);
cin>>n>>k;
memset(f[0],0x3f,(n+1)<<2);
f[0][0]=0;
for(int i=1,l,r,p,h,t;i<=k;i++){
cin>>l>>r;
p=i&1;
memcpy(f[p],f[p^1],(n+1)<<2);
h=1,t=0;
for(int j=r;~j;j--){
while(h<=t&&f[p^1][q[t]]>f[p^1][r-j]) t--;
q[++t]=r-j;
while(h<=t&&q[h]<l-j) h++;
f[p][j]=min(f[p][j],f[p^1][q[h]]+1);
}
h=1,t=0;
for(int j=0;j<=min(n,r);j++){
while(h<=t&&f[p^1][q[t]]>f[p^1][j]) t--;
q[++t]=j;
while(h<=t&&q[h]<j-r+l) h++;
f[p][j]=min(f[p][j],f[p^1][q[h]]+2);
}
}
if(f[k&1][n]==0x3f3f3f3f) cout<<"Hungry\n";
else cout<<"Full\n"<<f[k&1][n]<<'\n';
return 0;
}

浙公网安备 33010602011771号