Time Limit: 60 Sec
Memory Limit: 256 MB
Description
A 国共有 n 座城市,这些城市由 n-1 条道路相连,使得任意两座城市可以互达,且路径唯一。每座城市都有一个
幸运数字,以纪念碑的形式矗立在这座城市的正中心,作为城市的象征。一些旅行者希望游览 A 国。旅行者计划
乘飞机降落在 x 号城市,沿着 x 号城市到 y 号城市之间那条唯一的路径游览,最终从 y 城市起飞离开 A 国。
在经过每一座城市时,游览者就会有机会与这座城市的幸运数字拍照,从而将这份幸运保存到自己身上。然而,幸
运是不能简单叠加的,这一点游览者也十分清楚。他们迷信着幸运数字是以异或的方式保留在自己身上的。例如,
游览者拍了 3 张照片,幸运值分别是 5,7,11,那么最终保留在自己身上的幸运值就是 9(5 xor 7 xor 11)。
有些聪明的游览者发现,只要选择性地进行拍照,便能获得更大的幸运值。例如在上述三个幸运值中,只选择 5
和 11 ,可以保留的幸运值为 14 。现在,一些游览者找到了聪明的你,希望你帮他们计算出在他们的行程安排中
可以保留的最大幸运值是多少。
Input
第一行包含 2 个正整数 n ,q,分别表示城市的数量和旅行者数量。第二行包含 n 个非负整数,其中第 i 个整
数 Gi 表示 i 号城市的幸运值。随后 n-1 行,每行包含两个正整数 x ,y,表示 x 号城市和 y 号城市之间有一
条道路相连。随后 q 行,每行包含两个正整数 x ,y,表示这名旅行者的旅行计划是从 x 号城市到 y 号城市。N
<=20000,Q<=200000,Gi<=2^60
Output
输出需要包含 q 行,每行包含 1 个非负整数,表示这名旅行者可以保留的最大幸运值。
题目分析
时间限制看着就吓人
解法一:树上倍增
用
储存
到
的
级祖先的线性基
同样也要用
记录u的祖先
那么我们可以在预处理fa数组的时候同时处理a数组
for(int i=1;(1<<i)<=n;++i)
for(int u=1;u<=n;++u)
{
fa[u][i]=fa[fa[u][i-1]][i-1];
a[u][i]=merge(a[u][i-1],a[fa[u][i-1]][i-1]);
}
注意到这里fa[u][i]的值似乎被合并了两次
但其实是没有关系的
由于已经插入过一次
如果再次插入
的值,他会在一系列操作后变成0而被忽略
与处理完后对于每个询问
找到LCA(u,v)
用树上倍增合并u到lca和v到lca路径上的线性基
最后在得到的线性基中查询即可
int u=read(),v=read();
int lca=LCA(u,v); node tt;
tt.ins(val[u]);//因为有可能存在u==v,所以要先插入val[lca]
for(int i=15;i>=0;--i)
{
if(dep[fa[u][i]]>=dep[lca])tt=merge(tt,a[u][i]),u=fa[u][i];
if(dep[fa[v][i]]>=dep[lca])tt=merge(tt,a[v][i]),v=fa[v][i];
}
printf("%lld\n",qmax(tt));
完整代码
#include<iostream>
#include<cmath>
#include<algorithm>
#include<queue>
#include<cstring>
#include<cstdio>
using namespace std;
typedef long long lt;
lt read()
{
lt f=1,x=0;
char ss=getchar();
while(ss<'0'||ss>'9'){if(ss=='-')f=-1;ss=getchar();}
while(ss>='0'&&ss<='9'){x=x*10+ss-'0';ss=getchar();}
return f*x;
}
const int maxn=20010;
int n,m;
lt val[maxn];
struct edge{int v,nxt;}E[maxn<<1];
int head[maxn],tot;
int fa[maxn][20],dep[maxn];
struct node
{
lt d[63];
node(){memset(d,0,sizeof(d));}
void ins(lt x)
{
for(lt i=62;i>=0;--i)
if(x&((lt)(1)<<i))
{
if(!d[i]){d[i]=x;break;}
else x^=d[i];
}
}
}a[maxn][15];
void add(int u,int v)
{
E[++tot].nxt=head[u];
E[tot].v=v;
head[u]=tot;
}
void dfs(int u,int pa)
{
a[u][0].ins(val[u]);
for(int i=head[u];i;i=E[i].nxt)
{
int v=E[i].v;
if(v==pa) continue;
fa[v][0]=u; dep[v]=dep[u]+1;
a[v][0].ins(val[u]);
dfs(v,u);
}
}
int LCA(int u,int v)
{
if(dep[u]<dep[v])swap(u,v);
int d=dep[u]-dep[v];
for(int i=0;(1<<i)<=d;++i)
if(d&(1<<i)) u=fa[u][i];
if(u==v)return u;
for(int i=(int)log(n);i>=0;--i)
if(fa[u][i]!=fa[v][i])
u=fa[u][i],v=fa[v][i];
return fa[u][0];
}
node merge(node u,node v)
{
for(int i=62;i>=0;--i)
if(v.d[i])u.ins(v.d[i]);
return u;
}
lt qmax(node tt)
{
lt ans=0;
for(int i=62;i>=0;--i)
if((ans^tt.d[i])>ans)ans^=tt.d[i];
return ans;
}
int main()
{
n=read();m=read();
for(int i=1;i<=n;++i)val[i]=read();
for(int i=1;i<n;++i)
{
int u=read(),v=read();
add(u,v);add(v,u);
}
dep[1]=1; dfs(1,0);
for(int i=1;(1<<i)<=n;++i)
for(int u=1;u<=n;++u)
{
fa[u][i]=fa[fa[u][i-1]][i-1];
a[u][i]=merge(a[u][i-1],a[fa[u][i-1]][i-1]);
}
while(m--)
{
int u=read(),v=read();
int lca=LCA(u,v); node tt;
tt.ins(val[u]);
for(int i=15;i>=0;--i)
{
if(dep[fa[u][i]]>=dep[lca])tt=merge(tt,a[u][i]),u=fa[u][i];
if(dep[fa[v][i]]>=dep[lca])tt=merge(tt,a[v][i]),v=fa[v][i];
}
printf("%lld\n",qmax(tt));
}
return 0;
}
解法二:树链剖分
树剖后线段树上每个节点保存其对应区间的线性基
对每个询问只要在线段树上查找对应区间合并
最后在答案线性基中查找就好
理论上时间复杂度应该和倍增差不多
但是luogu上还是要开O2才过最后一个点
可能是我脸黑(?)
完整代码
#include<iostream>
#include<cmath>
#include<algorithm>
#include<queue>
#include<cstring>
#include<cstdio>
using namespace std;
typedef long long lt;
lt read()
{
lt f=1,x=0;
char ss=getchar();
while(ss<'0'||ss>'9'){if(ss=='-')f=-1;ss=getchar();}
while(ss>='0'&&ss<='9'){x=x*10+ss-'0';ss=getchar();}
return f*x;
}
void print(lt x)
{
if(x<0){putchar('-');x=-x;}
if(x>9) print(x/10);
putchar(x%10+'0');
}
const int maxn=20010;
int n,m;
lt val[maxn];
struct edge{int v,nxt;}E[maxn<<1];
int head[maxn],tot;
int fa[maxn],dep[maxn],son[maxn],cnt;
int size[maxn],top[maxn],num[maxn],pos[maxn];
lt a[maxn<<2][62],ans[65],tp[65];
void add(int u,int v)
{
E[++tot].nxt=head[u];
E[tot].v=v;
head[u]=tot;
}
void ins(lt *tt,lt x)
{
for(lt i=60;i>=0;--i)
if(x&((lt)(1)<<i))
{
if(!tt[i]){tt[i]=x;break;}
else x^=tt[i];
}
}
void merge(lt *a,lt *b)
{
for(int i=60;i>=0;--i)
if(b[i])ins(a,b[i]);
}
lt qmax(lt *tt)
{
lt ans=0;
for(int i=60;i>=0;--i)
if((ans^tt[i])>ans)ans^=tt[i];
return ans;
}
void dfs1(int u,int pa)
{
size[u]=1;
for(int i=head[u];i;i=E[i].nxt)
{
int v=E[i].v;
if(v==pa) continue;
fa[v]=u; dep[v]=dep[u]+1;
dfs1(v,u);
size[u]+=size[v];
if(size[v]>size[son[u]])son[u]=v;
}
}
void dfs2(int u,int tp)
{
top[u]=tp; num[u]=++cnt; pos[cnt]=u;
if(son[u])dfs2(son[u],tp);
for(int i=head[u];i;i=E[i].nxt)
{
int v=E[i].v;
if(v==fa[u]||v==son[u])continue;
dfs2(v,v);
}
}
void build(int s,int t,int p)
{
if(s==t){ins(a[p],val[pos[s]]);return;}
int mid=s+t>>1,lc=p<<1,rc=p<<1|1;
build(s,mid,lc);build(mid+1,t,rc);
merge(a[p],a[lc]); merge(a[p],a[rc]);
}
void get(int ll,int rr,int s,int t,int p)
{
if(ll<=s&&t<=rr)return merge(tp,a[p]);
int mid=s+t>>1;
if(ll<=mid) get(ll,rr,s,mid,p<<1);
if(rr>mid) get(ll,rr,mid+1,t,p<<1|1);
}
lt solve(int u,int v)
{
memset(ans,0,sizeof(ans));
while(top[u]!=top[v])
{
if(dep[top[u]]<dep[top[v]])swap(u,v);
memset(tp,0,sizeof(tp));
get(num[top[u]],num[u],1,n,1);
merge(ans,tp);
u=fa[top[u]];
}
memset(tp,0,sizeof(tp));
if(dep[v]<dep[u])swap(u,v);
get(num[u],num[v],1,n,1);
merge(ans,tp);
return qmax(ans);
}
int main()
{
n=read();m=read();
for(int i=1;i<=n;++i)val[i]=read();
for(int i=1;i<n;++i)
{
int u=read(),v=read();
add(u,v);add(v,u);
}
dep[1]=1;
dfs1(1,0); dfs2(1,1);
build(1,n,1);
while(m--)
{
int u=read(),v=read();
print(solve(u,v)); putchar('\n');
}
return 0;
}