大概套路:
条件:问题可以拆成一些部分之和的最值,且互不干涉,转移范围是一定的,而且要么可直接转移,要么具有可差分性
Cashback
https://www.gxyzoj.com/d/hzoj/p/4376
结论只推到一定是 \(c\) 的倍数或是小于等于 \(c\),但是其实在长度超过 \(c\) 的时候一定是越短越好
因为长度变长,最小值可能变小,所以可以直接分为两种情况
记当前点去掉的数之和最大为 \(f_i\),因为长度小于 \(c\) 时一定是无用的,直接从 \(f_{i-1}\) 转移即可
对于长度恰好为 \(c\) 的,可以找 \(i-c+1\) 到 \(i\) 的最小值,单调队列维护即可
Cutlet
https://www.gxyzoj.com/d/hzoj/p/4382
这道题有两维限制,朴素的,设 \(f_{i,j}\) 表示当前是第 \(i\) 个时刻,未被煎的一面的时长为 \(j\)
但是注意到,不属于区间的点是可以统一计算的,所以只用考虑区间内
又注意到,其实翻 0,1,2 次就是所有的情况了,因为可以把不与前后相连的同一面朝下时间连一起
所以将 \(i\) 的意义改为第 \(i\) 个区间即可
但是背包转移的时间复杂度大,因为是连续的,考虑单调队列
0 直接转移,2 因为朝上面不变,所以只考虑加上了多少
1 比较复杂,假设区间长度为 \(x\),右端点为 \(r\),那么转移范围就是 \(r-j-x\) 到 \(r-j\)
注意,这里 \(j\) 要倒着枚举
点击查看代码
#include<cstdio>
#include<algorithm>
using namespace std;
int n,k,l[105],r[105],q[200005],f[105][200005],tail,head;
int main()
{scanf("%d%d",&n,&k);n=n*2;for(int i=1;i<=k;i++){scanf("%d%d",&l[i],&r[i]);}for(int i=0;i<=k;i++){for(int j=0;j<=n;j++){f[i][j]=1e9;}}f[0][0]=0;for(int i=1;i<=k;i++){for(int j=0;j<=n;j++){f[i][j]=f[i-1][j];}head=1,tail=0;int x=r[i]-l[i];for(int j=0;j<=r[i];j++){if(head<=tail&&q[head]<j-x) head++;while(head<=tail&&f[i-1][q[tail]]>f[i-1][j]) tail--;q[++tail]=j;f[i][j]=min(f[i][j],f[i-1][q[head]]+2);}head=1,tail=0;for(int j=r[i];j>=0;j--){if(head<=tail&&q[head]<r[i]-j-x) head++;int tmp=r[i]-j;while(head<=tail&&f[i-1][q[tail]]>f[i-1][tmp]) tail--;q[++tail]=tmp;f[i][j]=min(f[i][j],f[i-1][q[head]]+1);}}if(f[k][n/2]!=1e9)printf("Full\n%d",f[k][n/2]);else printf("Hungry");return 0;//
}