【校内模拟】背包问题(带限制二维偏序的LIS)(线段树)

请确认你要搜索的关键词不是DP里面的背包问题而是一道叫做“背包问题”的数据结构题再来看。

简要题意:

你有 m m m个背包容量为 t i t_i ti,你有 n n n个物品,重量为 W i W_i Wi,权值为 V i V_i Vi

请你求出满足如下限制的最长的物品序列 p p p,假设长度为 l e n len len

  1. 对于 ∀ 1 ≤ i < l e n \forall 1\leq i < len 1i<len W p i ≤ W p i + 1 W_{p_i}\leq W_{p_{i+1}} WpiWpi+1
  2. 对于 ∀ 1 ≤ i < l e n \forall 1\leq i < len 1i<len V p i ≤ V p i + 1 V_{p_i}\leq V_{p_{i+1}} VpiVpi+1
  3. 存在一个长度为 l e n len len的背包序列 q q q,满足 ∀ 1 ≤ i ≤ l e n \forall 1\leq i\leq len 1ilen W p i ≤ t q i W_{p_i}\leq t_{q_i} Wpitqi

由于有多组数据,复杂度为 O ( n log ⁡ 2 n ) O(n\log^2n) O(nlog2n)的做法无法通过,要求复杂度为 O ( n log ⁡ n ) O(n\log n) O(nlogn)

题解:

考虑一个满足前两个限制的序列 p p p,然后拿出最大的 l e n len len个背包,如果这个都装不了那肯定gg。否则肯定是类似 L I S LIS LIS该怎么搞怎么搞。

按照 W W W排序后,考虑用权值线段树维护结尾为 V V V的最长序列。

问题在于哪些可以拿来更新,显然长度为 l e n len len必须在考虑了前 l e n len len个背包之后才能用于更新其他位置的答案,记录一下哪些更改需要在什么时候执行即可。


代码(其实应该用树状数组,失智用了ZKW线段树,懒得改了):

#include<bits/stdc++.h>
#define ll long long
#define re register
#define cs const

namespace IO{
	inline char gc(){
		static cs int Rlen=1<<22|1;
		static char buf[Rlen],*p1,*p2;
		return (p1==p2)&&(p2=(p1=buf)+fread(buf,1,Rlen,stdin),p1==p2)?EOF:*p1++;
	}
	template<typename T>inline T get(){
		char c;T num;
		while(!isdigit(c=gc()));num=c^48;
		while(isdigit(c=gc()))num=(num+(num<<2)<<1)+(c^48);
		return num;
	}
	inline int gi(){return get<int>();}
}
using namespace IO;

using std::cerr;
using std::cout;
typedef std::pair<int,int> pii;
#define fi first
#define se second

cs int N=1e5+7;

namespace SGT{
	cs int N=1<<18|7;
	int mx[N];int M;
	inline void build(int n){
		for(M=1;M<=n+1;M<<=1);
		memset(mx,0,sizeof mx);
	}
	inline void ins(int p,int v){
		if(mx[p+M]>=v)return ;
		for(mx[p+=M]=v,p>>=1;p;p>>=1)mx[p]=std::max(mx[p<<1],mx[p<<1|1]);
	}
	inline int query(int l,int r){
		int ans(0);
		for(l+=M-1,r+=M+1;l^r^1;l>>=1,r>>=1){
			if(~l&1)ans=std::max(ans,mx[l^1]);
			if(r&1) ans=std::max(ans,mx[r^1]);
		}return ans;
	}
}

int n,m,tot;
int t[N],b[N],f[N];
pii a[N];
std::vector<int> vec[N];

void solve(){
	n=gi();
	for(int re i=1;i<=n;++i){
		a[i].fi=gi(),a[i].se=gi();
		b[i]=a[i].se;
	}
	std::sort(b+1,b+n+1);
	tot=std::unique(b+1,b+n+1)-b-1;
	for(int re i=1;i<=n;++i){
		a[i].se=std::lower_bound(b+1,b+tot+1,a[i].se)-b;
	}
	SGT::build(tot);m=gi();
	for(int re i=1;i<=m;++i)t[i]=gi();
	std::sort(t+1,t+m+1);
	std::sort(a+1,a+n+1);
	std::reverse(t+1,t+m+1);
	std::reverse(a+1,a+n+1);
	int ans=0;
	for(int re i=1,lst=0;i<=n;++i){
		while(lst<m&&a[i].fi<=t[lst+1]){
			for(int re j:vec[lst])
			SGT::ins(a[j].se,f[j]);
			++lst;
		}
		if(a[i].fi<=t[lst]){
			f[i]=SGT::query(a[i].se,tot)+1;
			ans=std::max(ans,f[i]);
			if(f[i]<lst)SGT::ins(a[i].se,f[i]);
			else vec[f[i]+1].push_back(i);
		}
	}
	for(int re i=1;i<=n+1;++i)vec[i].clear(),f[i]=0;
	cout<<ans<<"\n";
}

//#undef zxyoi
signed main(){
#ifdef zxyoi
	freopen("bag.in","r",stdin);
#else
#ifndef ONLINE_JUDGE
	freopen("bag.in","r",stdin);freopen("bag.out","w",stdout);
#endif
#endif
	int T=gi();while(T--)solve();
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值