背包进阶(不带删的尺取和线段树分治的应用)

不带删的尺取。

这个技巧其实是一个运用双栈来模拟队列的一个应用,尺取可以理解为一个队列。

其实就是用一个栈来记队头,一个栈来记队尾,每个栈记对应的对应点记每个点的权值或下标,和它所对应栈的前缀。

每一次入栈时直接入栈(队头)更新前缀即可,而出栈时,若栈(队尾)已经空了,这时就应该把队头的栈直接倒着插入即可。

当然这样的方法仅仅适用于不能撤回的一些运算,学名消去律(当然你没有消去律直接用带删尺取就行了,没必要用这个)。比如说是最大公因数,背包这样的运算。

这个讲完了那先放一个例题:

[Integers Have Friends](Problem - 1548B - Codeforces)

这个直接我们发现这题的变量有点多,我们发现这就是一个同余的问题,如果同余作差那就是都是某个固定数的倍数了,我们只要求这个固定的数就可以了,这个数就是最大公因数了,那题目又说了 m2 说明区间最大公因数大于 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;
}
posted @ 2025-02-10 20:52  hnczy  阅读(16)  评论(0编辑  收藏  举报