【CodeForces】【單調隊列優化DP】939F Cutlet

CodeForces 939F Cutlet

題目大意

有一塊牛排需要兩面都需要煎 NN 秒,現僅有 KK 個時間段 [Li,Ri][L_i, R_i] 可以用來翻面,這段時間內可以任意翻轉這塊牛排。

問最少翻多少次使得牛排兩面都煎了 NN 秒。

分析

神奇的 DP。

定義狀態 f(i,j)f(i, j) 爲在前 ii 個時間段中,當前朝上的那一面已經被烤了 jj 秒鐘的最小翻轉次數。

那麼不難列出狀態轉移方程:

  • 當我們不翻轉時:f(i,j)=f(i1,j)f(i, j) = f(i - 1, j)
  • 當我們翻轉一次時: f(i,j)=f(i1,ij)+1f(i, j) = f(i - 1, i - j) + 1,這個轉移僅當在區間 [Li,Ri][L_i, R_i] 內發生。

顯然這個轉移是 O(N2K)O(N^2K) 的。我們必須考慮優化


結論: 當我們在一個區間內最多翻轉兩次牛排。

證明: 當我們在一個區間裏翻轉三次及以上的牛排時,我們可以發現僅隔一段的兩段時間所煎的牛排的面是相同的,於是我們可以通過等價的交換區間來減少牛排翻面的次數。

注意我們翻轉兩次牛排是爲了僅讓另一面煎一段時間,而出來時仍然是開始時的那一面。可以證明這種操作是有必要的。


當我們僅翻轉一次時,設翻轉後兩面煎的時間相差 kk ,翻轉的時刻爲 jj ,可以得到翻轉後朝上的那一面的煎的時間爲 RijR_i-j ,朝下的那一面的煎的時間就是 RijkR_i - j - k

由於翻過來後兩面是互換了的,所以有 f(i,j)=min{f(i1,Rijk)}+1f(i, j) = \min\{f(i - 1, R_i - j - k)\} + 1

不難發現有決策單調性,於是採用單調隊列維護決策點。

考慮直接枚舉 j+kj + k ,然後發現這個轉移要倒着枚舉。

當我們翻轉了兩次時,還是設兩面煎的時間相差 kk,又由於翻了兩次後還是這一面朝下,所以得到: f(i,j)=min{f(i1,jk)}+2f(i, j) = \min\{ f(i - 1, j - k) \} + 2

同樣可以用單調隊列維護,但這個轉移要順着枚舉了。

故總時間複雜度爲 O(NK)O(NK)

然後爲了保證空間不炸我開了滾動數組。

參考代碼

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

const int Maxn = 200000;
const int Maxk = 100;
const int INF = 0x3f3f3f3f;

int N, K;
int L[Maxk + 5], R[Maxk + 5];

int f[2][Maxn + 5];

int q[Maxn + 5];

int main() {
#ifdef LOACL
	freopen("in.txt", "r", stdin);
	freopen("out.txt", "w", stdout);
#endif
	scanf("%d %d", &N, &K);
	for(int i = 1; i <= K; i++)
		scanf("%d %d", &L[i], &R[i]);
	memset(f[0], 0x3f, sizeof f[0]);
	f[0][0] = 0;
	int z = 1;
	for(int k = 1; k <= K; k++) {
		memcpy(f[z], f[z ^ 1], sizeof f[z]);
		int head = 1, tail = 0;
		for(int i = 0; i <= min(N, R[k]); i++) {
			while(head <= tail && f[z ^ 1][i] <= f[z ^ 1][q[tail]])
				tail--;
			q[++tail] = i;
			while(head <= tail && q[head] < i - (R[k] - L[k]))
				head++;
			f[z][i] = min(f[z][i], f[z ^ 1][q[head]] + 2);
		}
		head = 1, tail = 0;
		for(int i = R[k]; i >= 0; i--) {
			while(head <= tail && f[z ^ 1][R[k] - i] <= f[z ^ 1][q[tail]])
				tail--;
			q[++tail] = R[k] - i;
			while(head <= tail && q[head] < L[k] - i)
				head++;
			f[z][i] = min(f[z][i], f[z ^ 1][q[head]] + 1);
		}
		z ^= 1;
	}
	if(f[z ^ 1][N] >= INF) puts("Hungry");
	else printf("Full\n%d\n", f[z ^ 1][N]);
	return 0;
}
發佈了155 篇原創文章 · 獲贊 84 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章