【SCOI2016】美味(主席树,贪心)
看到异或最大,第一反应是用 01trie 做。
但是 01trie 不能实现区间加法,所以不好做。
看回题目,从最简单的思路去想:
设 \(ans\) 能使得 \(ans\ \operatorname{xor}\ b\) 最大。每次将 \(b\) 二进制拆分,设二进制下 \(b\) 的第 \(i\) 位为 \(b_i\)(第 \(0\) 位为最低位),\(ans\) 的第 \(i\) 位为 \(ans_i\)。
然后我们贪心地从高位往低位考虑如何取 \(ans\)。假设当前考虑取第 \(i\) 位,前面取的总和为 \(sum\)(即 \(sum=\sum_{j>i}ans_j\times 2^j\))。显然,\(ans_i\) 取 \(b_i\operatorname{xor}1\) 最优。
但是这样取可能导致我们无法找到一个合法的 \(a_i+x=ans\),所以我们考虑当 \(ans_i\) 取 \(b_i\operatorname{xor}1\) 时,\(ans\) 的取值会在哪个范围。
易得此时 \(ans\in [sum+(b_i\operatorname{xor}1)\times2^i,sum+(b_i\operatorname{xor}1)\times2^{i}+2^i-1]\)。
那么对应合法的 \(a_i=ans-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]\)。
那么我们只用找到是否存在这么一个 \(a_i\) 在这个范围里就好了。如果存在,\(ans_i\) 就能取 \(b_i\operatorname{xor}1\),否则只能取 \(b_i\)。
我们再看一下现在要维护的东西:\(m\log (10^5)\) 次询问,每次给出两个区间 \([l,r]\) 和 \([v_1,v_2]\),问是否存在 \(a_i\in[v_1,v_2]\)(\(l\leq i\leq r\))。
这种形式就很熟悉了,我们可以使用可持久化权值线段树来维护这个东西。
代码中的细节还是挺多的,如下:
#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;
}