CF939F Cutlet 题解

CF939F Cutlet 题解

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


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

fi,j=min{fi1,jfi1,ij+1if lir

显然,这是 O(n2) 的,并没有什么前途。

注意到 k 很小,可以容纳一个 O(nk) 的算法。显然上面的那个转移方程中不在 [l,r] 范围内的都是没有用的,所以修改状态设计为:设 fi,j 表示对于前 i 个区间,到了 ri 时间,反面烤了 j 分钟的最小翻转次数。

但是这样的状态设计使得我们的翻转需要面向区间来考虑。发现一个区间内最多翻转两次,下面分类讨论:

  • 只翻转一次,那么枚举一个时间 k,代表翻转过后正面多烤k 分钟,那么有 0krl。由状态定义得,正面在本区间烤了 rj 分钟;又因为翻转之后正面多烤了 k 分钟,那么翻转过后的那一刻正面烤了 rjk 分钟。这个正面在翻转前是背面,所以这种情况的转移方程就是

    fi,j=min{fi,rjk+1} .

    因为 krl,所以得出 rjklj,也就是说这个最优决策点在 [lj,rj] 这个范围内,因此用单调队列维护这个最优决策点。

  • 翻转两次,同理枚举一个时间 k 表示翻转之后正面多烤了 k 分钟,同样有 krl。因为翻转两次等价于将当前的反面翻到正面烤了 k 分钟再翻回来,于是有转移方程

    fi,j=min{fi1,jk+2} .

    同样由 krl 得出 jkjr+l,也满足位于一个 [jr+l,j],同样用单调队列维护。

需要注意的是,翻转一次的情况中随着 j 的增大,决策区间是向左移的,所以 j 要倒推;而在翻转两次的情况中,j 增大时决策区间是正常地往右移,所以顺推。

另外,这个转移方程中只用到了 i 和 i1,所以需要用滚动数组优化空间。

#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 @   Laoshan_PLUS  阅读(4)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示