CodeForces 939F Cutlet
題目大意
有一塊牛排需要兩面都需要煎 秒,現僅有 個時間段 可以用來翻面,這段時間內可以任意翻轉這塊牛排。
問最少翻多少次使得牛排兩面都煎了 秒。
分析
神奇的 DP。
定義狀態 爲在前 個時間段中,當前朝上的那一面已經被烤了 秒鐘的最小翻轉次數。
那麼不難列出狀態轉移方程:
- 當我們不翻轉時:;
- 當我們翻轉一次時: ,這個轉移僅當在區間 內發生。
顯然這個轉移是 的。我們必須考慮優化
結論: 當我們在一個區間內最多翻轉兩次牛排。
證明: 當我們在一個區間裏翻轉三次及以上的牛排時,我們可以發現僅隔一段的兩段時間所煎的牛排的面是相同的,於是我們可以通過等價的交換區間來減少牛排翻面的次數。
注意我們翻轉兩次牛排是爲了僅讓另一面煎一段時間,而出來時仍然是開始時的那一面。可以證明這種操作是有必要的。
當我們僅翻轉一次時,設翻轉後兩面煎的時間相差 ,翻轉的時刻爲 ,可以得到翻轉後朝上的那一面的煎的時間爲 ,朝下的那一面的煎的時間就是 。
由於翻過來後兩面是互換了的,所以有 。
不難發現有決策單調性,於是採用單調隊列維護決策點。
考慮直接枚舉 ,然後發現這個轉移要倒着枚舉。
當我們翻轉了兩次時,還是設兩面煎的時間相差 ,又由於翻了兩次後還是這一面朝下,所以得到: 。
同樣可以用單調隊列維護,但這個轉移要順着枚舉了。
故總時間複雜度爲 。
然後爲了保證空間不炸我開了滾動數組。
參考代碼
#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;
}