背包进阶(不带删的尺取和线段树分治的应用)
不带删的尺取。
这个技巧其实是一个运用双栈来模拟队列的一个应用,尺取可以理解为一个队列。
其实就是用一个栈来记队头,一个栈来记队尾,每个栈记对应的对应点记每个点的权值或下标,和它所对应栈的前缀。
每一次入栈时直接入栈(队头)更新前缀即可,而出栈时,若栈(队尾)已经空了,这时就应该把队头的栈直接倒着插入即可。
当然这样的方法仅仅适用于不能撤回的一些运算,学名消去律(当然你没有消去律直接用带删尺取就行了,没必要用这个)。比如说是最大公因数,背包这样的运算。
这个讲完了那先放一个例题:
[Integers Have Friends](Problem - 1548B - Codeforces)
这个直接我们发现这题的变量有点多,我们发现这就是一个同余的问题,如果同余作差那就是都是某个固定数的倍数了,我们只要求这个固定的数就可以了,这个数就是最大公因数了,那题目又说了 m≥2 说明区间最大公因数大于 1
即可,这个就很适合尺取。代码如下:
#include<bits/stdc++.h>
#define int long long
#define pii pair<int,int>
using namespace std;
const int N=2E5+5;
int T,n,a[N],b[N],ans;
struct QUE{
pii st1[N],st2[N];
int top1,top2;
void clear(){
top1=top2=0;
}
//st1 栈顶是左端点
//st2 栈顶是右端点
void push(int x){
top2++;
st2[top2]={(top2!=1?__gcd(b[x],st2[top2-1].first):b[x]),b[x]};
}
void pop(){
if(top1)--top1;
else {
while(top2){
top1++;
st1[top1].second=st2[top2].second;
st1[top1] .first =(top1!=1?__gcd(st1[top1].second,st1[top1-1].first):st1[top1].second);
top2--;
}
--top1;
}
}
int query(){
return st2[top2].first;
}
bool check(){
if(!top1)return st2[top2].first==1;
if(!top2)return st1[top1].first==1;
return __gcd(st1[top1].first,st2[top2].first)==1;
}
}Q;
signed main(){
scanf("%lld",&T);
while(T--){
scanf("%lld",&n);
Q.clear();
for(int i=1;i<=n;i++)
scanf("%lld",a+i);
for(int i=1;i<n;i++)
b[i]=abs(a[i+1]-a[i]);
int ans=1;
for(int r=1,l=1;r<n;r++){
Q.push(r);
while(Q.check())//若 gcd ==1
Q.pop(),l++;
//cout<<l<<" "<<r<<" "<<Q.top2<<endl;
ans=max(r-l+2,ans);
}
printf("%lld\n",ans);
}
return 0;
}
结合背包其实也不难,这里给出这一道 小 ω 的仙人掌
小 ω 有 s(s<=10000) 个物品,每个物品有一定的大小与权值。
她可以从任意第 L 个物品走到第 R 个物品,这个区间内的物品可以选或者不选。
她取出的物品大小和必须为 w(w<=5000) ,权值和必须小于等于 k。
她想知道这个区间最短是多少。
你能告诉她吗?
这道题也是显然可以用不带删的尺取的,但是有点卡空间,你必须把两个栈都放在一起,以压缩空间才可以。
#include<bits/stdc++.h>
using namespace std;
const int N=1e4+5,inf=1e9;
int n,w,k,a[N],b[N];
struct node {
int id;
int f[5005];
};
void tomax(int &x,int y) {
if(x<y)x=y;
}
struct QUE {
node st[N];
int top1,top2;
void clear() {
top1=n+1;
top2=0;
}
void push(int x) {
top2++;
st[top2].id=x;
if(top2==1) {
for(int i=1; i<=w; i++)
st[top2].f[i]=inf;
st[top2].f[0]=0;
st[top2].f[a[x]]=b[x];
} else {
for(int i=w; i>=a[x]; i--)
st[top2].f[i]=min(st[top2-1].f[i-a[x]]+b[x],st[top2-1].f[i]);
for(int i=0; i<a[x]; i++)
st[top2].f[i]=st[top2-1].f[i];
}
}
//top1= 队头
//top2= 队尾
void pop() {
if(top1<=n)++top1;
else {
while(top2) {
top1--;
int x=st[top1].id=st[top2].id;
if(top1==n) {
for(int i=1; i<=w; i++)
st[top1].f[i]=inf;
st[top1].f[0]=0;
st[top1].f[a[x]]=b[x];
} else {
for(int i=w; i>=a[x]; i--)
st[top1].f[i]=min(st[top1+1].f[i-a[x]]+b[x],st[top1+1].f[i]);
for(int i=0; i<a[x]; i++)
st[top1].f[i]=st[top1+1].f[i];
}
top2--;
}
++top1;
}
}
int query() {
if(top1==n+1)return st[top2].f[w];
if(!top2)return st[top1].f[w];
int mi=inf;
for(int i=0; i<=w; i++)
mi=min(mi,st[top2].f[i]+st[top1].f[w-i]);
return mi;
}
} Q;
signed main() {
scanf("%d%d%d",&n,&w,&k);
for(int i=1; i<=n; i++)
scanf("%d%d",a+i,b+i);
Q.clear() ;
Q.push(1);
int ans=1e9;
for(int l=1,r=2; l<=n; l++) {
if(l!=1)Q.pop();
while((Q.query()>k&&r<=n)||(r<=l))Q.push(r),r++;
if(Q.query()<=k) ans=min(ans,r-l);
//cout<<l<<" "<<r<<endl;
}
if(ans==1e9) {
cout<<-1;
} else printf("%d",ans);
return 0;
}
线段树分治
这个技巧也是老生常谈了,很简单,就是若一个物品又一定的售卖时间,这个就得用线段树分治来写了。
这个也不是只能用在背包上,很多其他的算法也能结合。
这里也放一道例题:
[New Year Shopping](New Year Shopping - 洛谷 | 计算机科学教育新生态 (luogu.com.cn))
这道题也是很板的线段树分治了,直接离线搞一下,搞一个自上而下的背包数组来记即可。
#include<bits/stdc++.h>
#define int long long
#define ls p<<1
#define rs p<<1|1
using namespace std;
const int N=20000,M=4005;
struct node{
int c,h;
};
struct QUE{
int id,c;
};
void tomax(int &x,int y){
if(x<y)x=y;
}
int n,p,f[20][M],q,ans[N+5];
vector<node>e[N+1<<2];
vector<QUE>Q[N+5];
void change(int p,int l,int r,int L,int R,node u){
if(L<=l&&r<=R){
e[p].push_back(u);
return ;
}
int mid=l+r>>1;
if(L<=mid)change(ls,l,mid,L,R,u);
if(R>mid)change(rs,mid+1,r,L,R,u);
}
void dfs(int p,int l,int r,int dep){
if(dep)memcpy(f[dep],f[dep-1],sizeof f[dep]);
for(auto tmp:e[p]){
int c=tmp.c ,h=tmp.h;
for(int i=M-1;i>=c;i--)tomax(f[dep][i],f[dep][i-c]+h);
}
if(l==r){
for(auto tmp:Q[l])ans[tmp.id]=f[dep][tmp.c];
return ;
}
int mid=l+r>>1;
dfs(ls,l,mid,dep+1),dfs(rs,mid+1,r,dep+1);
}
signed main(){
scanf("%lld%lld",&n,&p);
for(int i=1,c,h,t;i<=n;i++){
scanf("%lld%lld%lld",&c,&h,&t);
change(1,1,N,t,t+p-1,{c,h});
}
scanf("%lld",&q);
for(int i=1,a,b;i<=q;i++){
scanf("%lld%lld",&a,&b);
Q[a].push_back({i,b});
}
dfs(1,1,N,0);
for(int i=1;i<=q;i++)
printf("%lld\n",ans[i]);
return 0;
}