CF939F Cutlet
传送门
题意
有一块烤肉,\(2n+1\) 个时刻,\(2n\)个时间段,每一时间段只能烤一面烤肉, 要求正反两面分别\(n\)个时间, 给你\(k\)个无交区间,你可以在这些区间内给烤肉翻面,求最小翻面次数。\(n \leq 1e5, k \leq 100\)
题解
看到题感觉是一道带结论的dp,然后开始想,先证他一手假贪心,然后转化为这么一个模型,你要选出最少的区间长度相加为\(n\), 保证区间端点在可翻转区间内。然后再想结论,有没有这样这种可能:一个区间只能翻转1次,错的,然后有结论1:
一个区间内最多翻转两次。我们翻转相当于取一些区间相加为\(n\), 你在一个可翻转区间里取两个答案区间, 为什么不合并成一个呢?所以只有两种情况,一个可翻转区间内取,翻转两次。 取的区间两个端点在不同可翻转区间,各翻转一次。
这给我们一个启发,多个区间合并起来更优。假如最优答案有很多区间,这些区间并没有把它所属可翻转区间占满,那我们把他们尽可能合并起来显然不劣。所以有结论: 一定存在一种最优答案: 其最多存在一个可翻转区间只取了一部分, 其他都取了整个区间或没取
读者可自证。
现在不仅可翻转区间可以整个取, 不可翻转区间也可以整个取(读自证),然后有做法,枚举这个只去一部分的区间,称之为零碎区间。我们枚举所有可翻转区间, 将其作为零碎区间所在的那个区间, 然后它左边要取若干个区间,右边取若干个区间, 我们设len为我们当前枚举的区间的长度,实际上是零碎区间的最大长度。那么只要左右区间长度和在\([n-len, n]\)之间即可,剩下的可以用零碎区间补齐,费用与零碎区间的长度显然无关,而与零碎区间相邻的区间是否选取有关(如果选了的话,直接把相邻区间的端点移到零碎区间的端点即可,无花费)
剩下的就暴力dp, 设\(f_{i, j}\)为前\(i\)个区间里选\(j\)长度需要多少区间, 再倒着dp一遍,然后枚举的时候用单调队列维护一手最小值即可。最近博客估计要大摆,不懂可看代码或问我(
实现
#include <iostream>
#include <cstdio>
using namespace std;
int read(){
int num=0, flag=1; char c=getchar();
while(!isdigit(c) && c!='-') c=getchar();
if(c == '-') c=getchar(), flag=-1;
while(isdigit(c)) num=num*10+c-'0', c=getchar();
return num*flag;
}
const int N = 1e5+50;
const int M = 205;
const int inf = 0x3f3f3f3f;
int n, m, k;
int ans = inf;
int f[M][N][2], p[M][N][2], ls[N];
int L[M], R[M], len[M], cnt=0, las=0, flag[M];
int st[N], tim[N], sl=0, sr=0;
int min(int a, int b){return a<b?a:b;}
int max(int a, int b){return a>b?a:b;}
int main(){
freopen("barbecue.in", "r", stdin);
freopen("barbecue.out", "w", stdout);
n=read(), m=n*2, k=read();
for(int i=1; i<=k; i++){
cnt++;
L[cnt]=R[cnt-1], R[cnt]=read(), len[cnt]=R[cnt]-L[cnt];
cnt++;
L[cnt]=R[cnt-1], R[cnt]=read(), len[cnt]=R[cnt]-L[cnt];
flag[cnt]=1;
}
cnt++; L[cnt]=R[cnt-1], R[cnt]=m, len[cnt]=R[cnt]-L[cnt];
for(int i=1; i<=n; i++) f[0][i][0] = f[0][i][1] = inf, p[cnt+1][i][0] = p[cnt+1][i][1] = inf;
for(int i=0; i<=cnt+1; i++){
f[i][0][1]=1;
p[i][0][1]=1;
}
for(int i=1; i<=cnt; i++){
for(int j=0; j<=n; j++){
f[i][j][0] = min(f[i-1][j][0], f[i-1][j][1]);
if(j-len[i] >= 0)
f[i][j][1] = min(f[i-1][j-len[i]][0] + 2, f[i-1][j-len[i]][1]);
else f[i][j][1] = inf;
}
}
for(int i=cnt; i; i--){
for(int j=0; j<=n; j++){
p[i][j][0] = min(p[i+1][j][0], p[i+1][j][1]);
if(j-len[i] >= 0)
p[i][j][1] = min(p[i+1][j-len[i]][0]+2, p[i+1][j-len[i]][1]);
else p[i][j][1] = inf;
}
}
ans = min(min(f[cnt][n][0], f[cnt][n][1]), min(p[1][n][0], p[1][n][1]));
for(int i=1; i<=cnt; i++){
if(!flag[i]) continue;
int res = inf;
int hav = len[i];
int l=n+1, r=n+1;
sl=1, sr=0;
for(int j=0; j<=n; j++){
int cl=max(0, n-hav-j), cr=n-j;
while(sl<=sr && tim[sl]>cr) sl++;
while(l > cl) {
l--;
int lp = min(p[i+1][l][0], p[i+1][l][1]);
while(sr>=sl && st[sr]>=lp) sr--;
st[++sr] = lp, tim[sr]=l;
}
res = min(res, min(f[i-1][j][0], f[i-1][j][1]) + 2 + st[sl]);
res = min(res, f[i-1][j][1] + st[sl]);
}
l=n+1, r=n+1;
sl=1, sr=0;
for(int j=0; j<=n; j++){
int cl=max(0, n-hav-j), cr=n-j;
while(sl<=sr && tim[sl]>cr) sl++;
while(l > cl) {
l--;
int lp = p[i+1][l][1];
while(sr>=sl && st[sr]>=lp) sr--;
st[++sr] = lp;
}
res = min(res, min(f[i-1][j][0], f[i-1][j][1]) + st[sl]);
}
ans = min(ans, res);
}
if(ans >= 2*n+100+m+n || ans >= inf-1000) printf("NO\n");
else printf("YES\n%d\n", ans);
return 0;
}
/***
20 8
4 4
9 9
13 13
18 18
22 22
27 27
31 31
36 36
****/