Codeforces 1479D. Odd Mineral Resource (2900)
给定一棵 \(n\) 个节点的树,每个点有颜色 \(c_i\),\(q\) 次查询,每次给定 \(u,v,l,r\),需要给出一个颜色 \(x\),使得 \(x\) 满足:
- \(x\in [l,r]\);
- \(x\) 在 \(u\) 到 \(v\) 的路径上出现了奇数次。
对于每组查询给出 \(x\),如果一组查询不存在合法的 \(x\),则输出 \(-1\)。
\(1\le n,m\le 3\times 10^5\)。
看到奇数次的条件容易想到异或操作,那么可以维护每个节点到根中权值在 \([l,r]\) 中的数的异或和,这部分用主席树即可实现。
具体地,设 \(f(u,v,l,r)\) 为 \(u\) 到 \(v\) 的路径上权值在 \([l,r]\) 中的数的异或和,设根为 \(1\) 号节点。
则 \(f(u,v,l,r)=f(1,u,l,r)\operatorname{xor}f(1,v,l,r)\operatorname{xor}f(1,\operatorname{lca}(u,v),l,r)\operatorname{xor}f(1,\operatorname{father}(\operatorname{lca}(u,v)),l,r)\)。
但是由于异或的数值域较小,很容易产生误判,解决的方法是将每个颜色和一个值域在 \([1,2^{64}-1]\) 的随机数进行对应,维护随机数的异或和。
查询直接在主席树上二分即可。判断权值在区间 \([l,r]\) 中的数的异或是否为 \(0\),若不为 \(0\),则在 \([l,mid]\) 和 \([mid+1,r]\) 中继续递归查找即可。
下面大致计算按照此随机操作后发生误判的概率:每次查询时,如果区间内的一个出现奇数次的随机数值等于区间内所有其他随机数值的异或和,那么就会导致出错。设区间长度为 \(l\),随机数值域为 \(V\),那么区间内每一个出现奇数次的随机数值都有一个不能取到的数,所以正确率就是 \(\prod\limits_{i=1}^{l}\frac{V-1}{V}\)。而总共会这样判断 \(O(q\log n)\) 次,即使每次长度取到最大为 \(n\),那么正确率也为 \((\frac{V−1}{V})^{nq\log n}\),通过计算可以发现取 \(V=2^{64}-1\) 时足以通过。
#include<bits/stdc++.h>
#define eb emplace_back
using namespace std;
typedef long long ll;
const int N=3e5+5;
struct Node{int ls,rs;ll val;}tr[N*20];
int n,m,ans,tot,col[N],dep[N],rt[N],fa[N][19];ll V[N];
vector<int>G[N];map<ll,int>mp;
inline void update(int old,int &x,int l,int r,int y,ll v){
tr[x=++tot].val=tr[old].val^v,tr[x].ls=tr[old].ls,tr[x].rs=tr[old].rs;
if(l<r){
int mid=l+r>>1;
y<=mid?update(tr[old].ls,tr[x].ls,l,mid,y,v):update(tr[old].rs,tr[x].rs,mid+1,r,y,v);
}
}
inline void findpos(int u,int v,int p,int fp,int l,int r){
if(l==r)return void(ans=l);int mid=l+r>>1;
if(tr[tr[u].ls].val^tr[tr[v].ls].val^tr[tr[p].ls].val^tr[tr[fp].ls].val)
findpos(tr[u].ls,tr[v].ls,tr[p].ls,tr[fp].ls,l,mid);
else findpos(tr[u].rs,tr[v].rs,tr[p].rs,tr[fp].rs,mid+1,r);
}
inline void query(int u,int v,int p,int fp,int l,int r,int L,int R){
if(L<=l&&R>=r){
if(tr[u].val^tr[v].val^tr[p].val^tr[fp].val)
findpos(u,v,p,fp,l,r);
return;
}
int mid=l+r>>1;
if(L<=mid)query(tr[u].ls,tr[v].ls,tr[p].ls,tr[fp].ls,l,mid,L,R);
if(R>mid)query(tr[u].rs,tr[v].rs,tr[p].rs,tr[fp].rs,mid+1,r,L,R);
}
inline void dfs(int x,int F){
update(rt[F],rt[x],1,n,col[x],V[col[x]]);
for(auto y:G[x])if(y^F)
fa[y][0]=x,dep[y]=dep[x]+1,dfs(y,x);
}
inline int lca(int x,int y){
if(dep[x]<dep[y])swap(x,y);
for(int i=18;~i;--i)if((dep[x]-dep[y])>>i&1)x=fa[x][i];
if(x==y)return x;
for(int i=18;~i;--i)if(fa[x][i]^fa[y][i])x=fa[x][i],y=fa[y][i];
return fa[x][0];
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i)scanf("%d",col+i);
for(int i=1,x,y;i<n;++i)scanf("%d%d",&x,&y),G[x].eb(y),G[y].eb(x);
unsigned seed=std::chrono::system_clock::now().time_since_epoch().count();
mt19937 rd(seed);
uniform_int_distribution<long long>dist(0,1e18);
for(int i=1;i<=n;++i)mp[V[i]=rd()]=i;
dfs(1,0);
for(int j=1;j<=18;++j)
for(int i=1;i<=n;++i)
fa[i][j]=fa[fa[i][j-1]][j-1];
while(m--){
int u,v,l,r,P;
scanf("%d%d%d%d",&u,&v,&l,&r),P=lca(u,v),ans=-1;
query(rt[u],rt[v],rt[P],rt[fa[P][0]],1,n,l,r),printf("%d\n",ans);
}
return 0;
}