闲话
有这样一个问题——一个长度为\(n\)的序列\(a_1-a_n\),\(q\)个询问,每次询问\(l,r\),选出\(\{a_l,a_{l+1}...a_{r}\}\)中一个子集使得子集内元素异或和最大/小。
第一次出现在HNOI模拟赛,当时的\(n,q\)只有大概\(3*10^4\)还是\(10^5\)的样子。然后毫不犹豫的写了个\(n\log^3n+q\log^2n\)的线性基ST表过了。。。
第二次出现在NOI模拟赛,数据范围大到了\(10^6\)!!!因为上一次IOI金牌爷laofu踩了标算。。。然后发现自己上次没听懂,只会写个两个\(\log\)的分治。
第三次出现在NOI.AC的NOIP lus模拟赛,然后发现自己还是没听懂。。。。。。
题目
是个巧(wu)妙(chi)的二合一,另一部分的思路来自洛谷P4151 [WC2011]最大XOR和路径
注意到线性基的一些特性:当向其中插入若干个位长为\(k\)的整数后,实际上真正插入到其中的只有\(k\)个数。也就是说,对于一个区间\([l,r]\),当我们从\(r\)到\(l\)依次尝试插入数的过程中,最多会产生\(k\)个本质不同的线性基,而且这些数在线性基内的插入行两两不同。
我们离线处理,对于右端点都在\(r\)的询问区间一起考虑。我们已知\([1,r]\)的线性基每一行被插入的数在原序列中的位置(从\(r\)到\(l\)插入)。对于一个询问,我们取出线性基中所有被插入位置\(\ge l\)的行来更新答案。
那么如何快速从\([1,r-1]\)的线性基变到\([1,r]\)呢?毫无疑问我们这时会先插入\(a_r\),设它插入到了第\(j\)行。那么原来在第\(j\)行的数现在就不会留在这里了,会\(xor\ a_r\)后继续尝试着插入下面的行。操作过程就是:把原来的数取出来,插入当前数,继续把后面原来的数取出来。。。如是循环。
总的复杂度变成了\((n+q)k\),十分优秀。
#include<bits/stdc++.h>
#define RG register
#define R RG int
#define G if(++ip==iend)fread(ip=buf,1,N,stdin)
using namespace std;
const int N=3e5+9,M=6e5+9;
char buf[N],*iend=buf+N,*ip=iend-1;
int he[N],ne[M],to[M],w[M],s[N],a[N],b[N],l[N],lb[39],at[39];
bool vis[N];
inline int in(){
G;while(*ip<'-')G;
R x=*ip&15;G;
while(*ip>'-'){x*=10;x+=*ip&15;G;}
return x;
}
inline void chkmn(R&x,R y){
if(x>y)x=y;
}
void dfs(R x){
vis[x]=1;
for(R y,i=he[x];i;i=ne[i])
if(!vis[y=to[i]])s[y]=s[x]^w[i],dfs(y);
}
int main(){
R n=in(),m=in(),q=in(),p=0,i,r,x,y;
for(i=1;i<n;++i){
x=in();y=in();
ne[++p]=he[x];to[he[x]=p]=y;
ne[++p]=he[y];to[he[y]=p]=x;
w[p]=w[p-1]=in();
}
dfs(1);
for(i=1;i<=m;++i)
a[i]=s[in()]^s[in()]^in();
memset(he+1,0,n<<2);
for(i=1;i<=q;++i){
b[i]=s[in()]^s[in()];
l[i]=in();ne[i]=he[r=in()];he[r]=i;
}
for(r=1;r<=m;++r){
x=a[r];p=r;
for(i=30;~i;--i)//更新线性基
if(1<<i&x){
if(!lb[i]){
lb[i]=x;at[i]=p;
break;
}
if(at[i]<p)
swap(lb[i],x),swap(at[i],p);
x^=lb[i];
}
for(x=he[r];x;x=ne[x])//求答案
for(i=30;~i;--i)
if(at[i]>=l[x])chkmn(b[x],b[x]^lb[i]);
}
for(i=1;i<=q;++i)
printf("%d\n",b[i]);
return 0;
}