SCOI2016 幸运数字
幸运数字
A 国共有 \(n\) 座城市,这些城市由 \(n - 1\) 条道路相连,使得任意两座城市可以互达,且路径唯一。每座城市都有一个幸运数字,以纪念碑的形式矗立在这座城市的正中心,作为城市的象征。一些旅行者希望游览 A 国。旅行者计划乘飞机降落在 \(x\) 号城市,沿着 \(x\) 号城市到 \(y\) 号城市之间那条唯一的路径游览,最终从 \(y\) 城市起飞离开 A 国。
在经过每一座城市时,游览者就会有机会与这座城市的幸运数字拍照,从而将这份幸运保存到自己身上。然而,幸运是不能简单叠加的,这一点游览者也十分清楚。他们迷信着幸运数字是以异或的方式保留在自己身上的。例如,游览者拍了 $ 3 $ 张照片,幸运值分别是 \(5\)、\(7\)、\(11\),那么最终保留在自己身上的幸运值就是 \(9\)(\(5 \mathbin{\text{xor}} 7 \mathbin{\text{xor}} 11\))。
有些聪明的游览者发现,只要选择性地进行拍照,便能获得更大的幸运值。例如在上述三个幸运值中,只选择 \(5\) 和 \(11\) ,可以保留的幸运值为 \(14\) 。现在,一些游览者找到了聪明的你,希望你帮他们计算出在他们的行程安排中可以保留的最大幸运值是多少。
暴力
考试的时候一眼看出来树剖+线段树+线性基,时间复杂度\(O(n\log^2 v+q\log^2 n\log^2 v)\),期望得分60分。
但是由于数据很弱,所以AC了。
另外更新一下对线性基的看法,这个东西比高斯消元常数小,所以还是有点用。
co int N=2e4+1;
int n,q,fa[N],dep[N],siz[N],son[N],top[N],pos[N],dfn,ref[N];
ull g[N];
vector<int> e[N];
il void dfs1(rg int x,rg int fa){
::fa[x]=fa,dep[x]=dep[fa]+1,siz[x]=1;
for(rg int i=0;i<e[x].size();++i){
rg int y=e[x][i];
if(y==fa) continue;
dfs1(y,x);
siz[x]+=siz[y];
if(siz[y]>siz[son[x]]) son[x]=y;
}
}
il void dfs2(rg int x,rg int top){
::top[x]=top,pos[x]=++dfn,ref[dfn]=x;
if(!son[x]) return;
dfs2(son[x],top);
for(rg int i=0;i<e[x].size();++i){
rg int y=e[x][i];
if(y==fa[x]||y==son[x]) continue;
dfs2(y,y);
}
}
struct node{
ull v[60];
il ull calc(){
rg ull ans=0;
for(rg int i=59;i>=0;--i)
if((ans^v[i])>ans) ans^=v[i];
return ans;
}
il void insert(rg ull g){
for(rg int i=59;g&&i>=0;--i) if(g>>i&1){
if(!v[i]) {v[i]=g;break;}
g^=v[i];
}
}
il node operator+(rg co node&b)co{
static node ans;ans=*this;
for(int i=59;i>=0;--i)
ans.insert(b.v[i]);
return ans;
}
}s[N*4];
il void build(rg int x,rg int l,rg int r){
if(l==r){
s[x].insert(g[ref[l]]);
return;
}
rg int mid=l+r>>1;
build(x<<1,l,mid),build(x<<1|1,mid+1,r);
s[x]=s[x<<1]+s[x<<1|1];
}
il node query(rg int x,rg int l,rg int r,rg int ql,rg int qr){
if(ql<=l&&r<=qr) return s[x];
rg int mid=l+r>>1;
if(qr<=mid) return query(x<<1,l,mid,ql,qr);
if(ql>mid) return query(x<<1|1,mid+1,r,ql,qr);
return query(x<<1,l,mid,ql,qr)+query(x<<1|1,mid+1,r,ql,qr);
}
il ull query(rg int x,rg int y){
static node ans;fill(ans.v,ans.v+60,0);
while(top[x]!=top[y]){
if(dep[top[x]]<dep[top[y]]) swap(x,y);
ans=ans+query(1,1,n,pos[top[x]],pos[x]);
x=fa[top[x]];
}
if(dep[x]<dep[y]) swap(x,y);
ans=ans+query(1,1,n,pos[y],pos[x]);
return ans.calc();
}
int main()
{
// freopen("lucky.in","r",stdin),freopen("lucky.out","w",stdout);
read(n),read(q);
for(rg int i=1;i<=n;++i) read(g[i]);
for(rg int i=1,x,y;i<n;++i){
read(x),read(y);
e[x].push_back(y),e[y].push_back(x);
}
dfs1(1,0);
dfs2(1,1);
build(1,1,n);
for(rg int x,y;q--;){
read(x),read(y);
printf("%llu\n",query(x,y));
}
return 0;
}
题解
事实上稍微写好一点就是3个log的。
https://www.cnblogs.com/YoungNeal/p/10216432.html
每次求出重心到每个点的线性基,只统计过重心的路径的答案即可。
如果一条路径只经过了一个点特判掉即可。时间复杂度\(O(q\log n\log v)\)。
struct node{
int64 v[61];
IN void clear(){
memset(v,0,sizeof v);
}
IN void insert(int64 x){
for(int i=60;x and i>=0;--i)if(x>>i&1){
if(!v[i]) {v[i]=x; return;}
x^=v[i];
}
}
IN int64 query(){
int64 ans=0;
for(int i=60;i>=0;--i)if((ans^v[i])>ans) ans^=v[i];
return ans;
}
};
IN node operator+(node a,CO node&b){
for(int i=60;i>=0;--i) a.insert(b.v[i]);
return a;
}
CO int N=2e4+10;
int64 val[N];
vector<int> to[N];
node bas[N];
vector<pair<int,int> > que[N];
int64 ans[200010];
int vis[N],siz[N],all;
pair<int,int> root;
void find_root(int x,int fa){
siz[x]=1;
pair<int,int> ans={0,x};
for(int y:to[x])if(!vis[y] and y!=fa){
find_root(y,x);
siz[x]+=siz[y];
ans.first=max(ans.first,siz[y]);
}
ans.first=max(ans.first,all-siz[x]);
root=min(root,ans);
}
int pos[N],tim;
void dfs(int x,int fa){
bas[x]=bas[fa],bas[x].insert(val[x]);
for(CO pair<int,int>&q:que[x])if(pos[q.first]==tim)
ans[q.second]=(bas[x]+bas[q.first]).query();
for(int y:to[x])if(!vis[y] and y!=fa) dfs(y,x);
}
void mark(int x,int fa){
pos[x]=tim;
for(int y:to[x])if(!vis[y] and y!=fa) mark(y,x);
}
void solve(int x){
vis[x]=1;
bas[x].clear(),bas[x].insert(val[x]);
pos[x]=++tim;
for(int y:to[x])if(!vis[y]) dfs(y,x),mark(y,x);
int old=all;
for(int y:to[x])if(!vis[y]){
root={all=siz[y]<siz[x]?siz[y]:old-siz[x],0},find_root(y,0);
solve(root.second);
}
}
int main(){
int n=read<int>(),m=read<int>();
for(int i=1;i<=n;++i) read(val[i]);
for(int i=1;i<n;++i){
int x=read<int>(),y=read<int>();
to[x].push_back(y),to[y].push_back(x);
}
for(int i=1;i<=m;++i){
int x=read<int>(),y=read<int>();
if(x==y) {ans[i]=val[x]; continue;} // edit 1: won't be calculated
que[x].push_back({y,i}),que[y].push_back({x,i});
}
root={all=n,0},find_root(1,0);
solve(root.second);
for(int i=1;i<=m;++i) printf("%lld\n",ans[i]);
return 0;
}