CF939F Cutlet 题解

CF939F Cutlet 题解

看了一眼竟没有思路,DP 还得再练啊。


首先容易发现正反面是对称的,所以可以钦定当前烤的永远是正面,然后可以设 \(f_{i,j}\) 表示烤到第 \(i\) 分钟、反面烤了 \(j\) 分钟的最小翻转次数。则有转移方程:

\[f_{i,j}=\min\begin{cases}f_{i-1,j}\\f_{i-1,i-j}+1&\text{if }l\le i\le r\end{cases} \]

显然,这是 \(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;
}
posted @ 2025-02-23 19:49  Laoshan_PLUS  阅读(61)  评论(0)    收藏  举报