题意:
有2∗n的时间去煎一块两面的肉,给你k个可以翻转的区间 [li,ri] [ l i , r i ] ,可以在区间内翻转任意次, 保证区间不相交
问是否存在合法的方案使得两面恰好都只煎了 n 分钟,并求最小翻转次数
n<=100000,k<=100
Solution:
考虑朴素的dp
f[i][j] f [ i ] [ j ] 表示前i秒,当前不在烤的面被烤过j秒,所需的最短翻转次数
转移 f[i][j]=f[i−1][j],f[i][j]=f[i−1][i−j]+1 f [ i ] [ j ] = f [ i − 1 ] [ j ] , f [ i ] [ j ] = f [ i − 1 ] [ i − j ] + 1
但是这样是 O(n2) O ( n 2 ) 的,需要优化
注意到第二个转移只在这k个区间里才会发生
那么我们可以转变一下状态:
f[i][j] f [ i ] [ j ] 表示前 ri r i 秒,当前不在烤的面被烤过j秒,所需的最短翻转次数
同时我们还发现每个区间肉最多被翻转2次,那么我们就可以分成翻一次和翻两次来讨论了
转移即为
f[i][j]=mink≤ri−lik=0(f[i−1][j−k])+2 f [ i ] [ j ] = m i n k = 0 k ≤ r i − l i ( f [ i − 1 ] [ j − k ] ) + 2 , f[i][j]=mink≤ri−lik=0(f[i−1][ri−j−k])+1 f [ i ] [ j ] = m i n k = 0 k ≤ r i − l i ( f [ i − 1 ] [ r i − j − k ] ) + 1
发现这两个转移分别都是满足单调性的,使用单调队列优化即可
代码:
#include<cstdio>
#include<iostream>
#define debug(x) l=l
using namespace std;
const int inf=1e9;
int n,m;
int f[110][100010];
int q[100010],h,t,l,r;
int main()
{
scanf("%d%d",&n,&m);
for (int i=1;i<=n;i++) f[0][i]=1e9;
f[0][0]=0;
for (int i=1;i<=m;i++)
{
scanf("%d%d",&l,&r);
h=1,t=0;
for (int j=0;j<=n;j++) f[i][j]=f[i-1][j];
for (int j=0;j<=r;j++)
{
debug(j);
if (j<=n)
{
while (h<=t&&f[i-1][j]<=f[i-1][q[t]])t--;
q[++t]=j;
}
while (h<=t&&q[h]<j-(r-l)) h++;
if (h<=t) debug(q[h]),debug(f[i][j]),f[i][j]=min(f[i][j],f[i-1][q[h]]+2),debug(f[i][j]);
}
h=1,t=0;
for (int j=r;j>=0;j--)
{
debug(j);
if (r-j<=n)
{
while (h<=t&&f[i-1][r-j]<=f[i-1][q[t]])t--;
q[++t]=r-j;
}
while (h<=t&&q[h]<l-j) h++;
if (h<=t) debug(q[h]),debug(f[i][j]),f[i][j]=min(f[i][j],f[i-1][q[h]]+1),debug(f[i][j]);
}
}
if (f[m][n]>=inf) printf("Hungry");
else printf("Full\n%d",f[m][n]);
}