【SCOI2016】美味(主席树,贪心)

10 篇文章 0 订阅
4 篇文章 0 订阅

看到异或最大,第一反应是用 01trie 做。

但是 01trie 不能实现区间加法,所以不好做。

看回题目,从最简单的思路去想:

a n s ans ans 能使得 a n s   xor ⁡   b ans\ \operatorname{xor}\ b ans xor b 最大。每次将 b b b 二进制拆分,设二进制下 b b b 的第 i i i 位为 b i b_i bi(第 0 0 0 位为最低位), a n s ans ans 的第 i i i 位为 a n s i ans_i ansi

然后我们贪心地从高位往低位考虑如何取 a n s ans ans。假设当前考虑取第 i i i 位,前面取的总和为 s u m sum sum(即 s u m = ∑ j > i a n s j × 2 j sum=\sum_{j>i}ans_j\times 2^j sum=j>iansj×2j)。显然, a n s i ans_i ansi b i xor ⁡ 1 b_i\operatorname{xor}1 bixor1 最优。

但是这样取可能导致我们无法找到一个合法的 a i + x = a n s a_i+x=ans ai+x=ans,所以我们考虑当 a n s i ans_i ansi b i xor ⁡ 1 b_i\operatorname{xor}1 bixor1 时, a n s ans ans 的取值会在哪个范围。

易得此时 a n s ∈ [ s u m + ( b i xor ⁡ 1 ) × 2 i , s u m + ( b i xor ⁡ 1 ) × 2 i + 2 i − 1 ] ans\in [sum+(b_i\operatorname{xor}1)\times2^i,sum+(b_i\operatorname{xor}1)\times2^{i}+2^i-1] ans[sum+(bixor1)×2i,sum+(bixor1)×2i+2i1]

那么对应合法的 a i = a n s − x a_i=ans-x ai=ansx 的取值范围也就出来了:

a i ∈ [ s u m + ( b i xor ⁡ 1 ) × 2 i − x , s u m + ( b i xor ⁡ 1 ) × 2 i + 2 i − 1 − x ] a_i\in[sum+(b_i\operatorname{xor}1)\times2^i-x,sum+(b_i\operatorname{xor}1)\times2^{i}+2^i-1-x] ai[sum+(bixor1)×2ix,sum+(bixor1)×2i+2i1x]

那么我们只用找到是否存在这么一个 a i a_i ai 在这个范围里就好了。如果存在, a n s i ans_i ansi 就能取 b i xor ⁡ 1 b_i\operatorname{xor}1 bixor1,否则只能取 b i b_i bi

我们再看一下现在要维护的东西: m log ⁡ ( 1 0 5 ) m\log (10^5) mlog(105) 次询问,每次给出两个区间 [ l , r ] [l,r] [l,r] [ v 1 , v 2 ] [v_1,v_2] [v1,v2],问是否存在 a i ∈ [ v 1 , v 2 ] a_i\in[v_1,v_2] ai[v1,v2] l ≤ i ≤ r l\leq i\leq r lir)。

这种形式就很熟悉了,我们可以使用可持久化权值线段树来维护这个东西。

代码中的细节还是挺多的,如下:

#include<bits/stdc++.h>

#define LN 18
#define N 200010
#define lc(u) t[u].ch[0]
#define rc(u) t[u].ch[1]

using namespace std;

struct Segment_Tree
{
	int ch[2],size;
}t[(N<<2)+N*LN];//主席数空间:4n+nlogn

int n,m,a[N];
int node,root[N];

void build(int &u,int l,int r)
{
	u=++node;
	if(l==r) return;
	int mid=(l+r)>>1;
	build(lc(u),l,mid);
	build(rc(u),mid+1,r);
}

void insert(int &u,int last,int l,int r,int x)
{
	u=++node;
	t[u]=t[last];
	t[u].size++;
	if(l==r) return;
	int mid=(l+r)>>1;
	if(x<=mid) insert(lc(u),lc(last),l,mid,x);
	else insert(rc(u),rc(last),mid+1,r,x);
}

bool query(int a,int b,int l,int r,int ql,int qr)
{
	if(ql<=l&&r<=qr) return t[b].size-t[a].size;//如果两次query分别求两棵树的size再相减判断的话可能会TLE,所以直接一次query判断
	int mid=(l+r)>>1;
	bool flag=false;
	if(ql<=mid) flag|=query(lc(a),lc(b),l,mid,ql,qr);
	if(qr>mid) flag|=query(rc(a),rc(b),mid+1,r,ql,qr);
	return flag;
}

int main()
{
	scanf("%d%d",&n,&m);
	build(root[0],0,1e5);//树的区间不是1到n,而是0到10^5,因为这是权值线段树
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
		insert(root[i],root[i-1],0,1e5,a[i]);
	}
	while(m--)
	{
		int b,x,l,r;
		scanf("%d%d%d%d",&b,&x,&l,&r);
		int sum=0;
		for(int i=17;i>=0;i--)//i要从17开始枚举,否则会WA,因为 2^16<=max ai<=2^17
		{
			int tmp=(b>>i)&1;
			tmp^=1;
			int minn,maxn;
			if(tmp)
			{
				minn=sum+(1<<i);
				maxn=sum+(1<<(i+1))-1;
			}
			else
			{
				minn=sum;
				maxn=sum+(1<<i)-1;
			}
			minn-=x;
			maxn-=x;//求出ai的范围
			bool flag=true;
			if(maxn<0) flag=false;//特判右端点小于0 
			else if(minn>1e5) flag=false;//特判左端点大于10^5
			else flag=query(root[l-1],root[r],0,1e5,max(minn,0),min(maxn,(int)1e5));//记得取max、min
			if(flag) sum+=tmp*(1<<i);
			else sum+=(tmp^1)*(1<<i);
		}
		printf("%d\n",sum^b);
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值