洛谷 P8965 坠梦 | Falling into Dream 题解

题目来源

Description

  • 给定一棵 n n n 个节点的无根树,边权为非负整数,节点编号 [ 1 , n ] [1,n] [1,n]
  • 对于给定的 q q q 组询问 ( x , y , l , r ) (x,y,l,r) (x,y,l,r),求 ⨁ i = l r ( d i s ( x , i ) ⊕ d i s ( y , i ) ) \bigoplus_{i=l}^r\Big(dis(x,i)\oplus dis(y,i)\Big) i=lr(dis(x,i)dis(y,i)),其中 d i s ( x , y ) dis(x,y) dis(x,y) 表示 x x x y y y 的唯一简单路径上边的边权异或和。
  • 1 ≤ n , q ≤ 1 0 6 1\le n,q\le10^6 1n,q106

Solution

首先考虑如何用 O ( 1 ) O(1) O(1) 的效率求出 d i s ( x , y ) dis(x,y) dis(x,y),于是我们可以很容易地想到设 f i f_i fi 表示节点 i i i 到根节点的路径上边的边权异或和,则显然 d i s ( x , y ) = f x ⊕ f y dis(x,y)=f_x\oplus f_y dis(x,y)=fxfy

简单证明一下其正确性:若设 l c a lca lca 表示 x x x y y y 的最近公共祖先,则显然 d i s ( x , y ) = d i s ( x , l c a ) ⊕ d i s ( y , l c a ) dis(x,y)=dis(x,lca)\oplus dis(y,lca) dis(x,y)=dis(x,lca)dis(y,lca),那么由于 a ⊕ a = 0 , a ⊕ 0 = a a\oplus a=0,a\oplus0=a aa=0,a0=a,且异或运算满足结合律,则 d i s ( x , y ) dis(x,y) dis(x,y) 就可以转化为 f x ⊕ f l c a ⊕ f y ⊕ f l c a f_x\oplus f_{lca}\oplus f_y\oplus f_{lca} fxflcafyflca d i s ( x , y ) = f x ⊕ f y dis(x,y)=f_x\oplus f_y dis(x,y)=fxfy

则我们只需 O ( n ) O(n) O(n) 预处理出 { f n } \{f_n\} {fn} 即可。

再考虑如何求出答案。
⨁ i = l r ( d i s ( x , i ) ⊕ d i s ( y , i ) ) = ⨁ i = l r ( f x ⊕ f i ⊕ f y ⊕ f i ) = ⨁ i = l r ( f x ⊕ f y ) \begin{aligned} \bigoplus_{i=l}^r\Big(dis(x,i)\oplus dis(y,i)\Big) &=\bigoplus_{i=l}^r\Big(f_x\oplus f_i\oplus f_y\oplus f_i\Big)\\ &=\bigoplus_{i=l}^r\Big(f_x\oplus f_y\Big) \end{aligned} i=lr(dis(x,i)dis(y,i))=i=lr(fxfifyfi)=i=lr(fxfy)
根据异或的性质,若 r − l + 1 r-l+1 rl+1 为偶数,显然答案即为 0 0 0;否则答案就为 f x ⊕ f y f_x\oplus f_y fxfy,可以 O ( 1 ) O(1) O(1) 处理每一次询问。

则总效率为 O ( n + q ) O(n+q) O(n+q)

Code

#include <bits/stdc++.h>
using namespace std;
int n,q,head[1000005],ver[2000005],edge[2000005],nxt[2000005],tot,f[1000005];
void add(int u,int v,int w){
    ver[++tot]=v,edge[tot]=w,nxt[tot]=head[u],head[u]=tot;
}
void work(int u,int fa){
    for (int i=head[u];i;i=nxt[i]){
        int v=ver[i],w=edge[i];
        if (v!=fa) f[v]=f[u]^w,work(v,u);
    }
}
int main(){
    scanf("%d%d",&n,&q);
    for (int i=1,u,v,w;i<n;i++) scanf("%d%d%d",&u,&v,&w),add(u,v,w),add(v,u,w);
    work(1,0);
    while (q--){
        int x,y,l,r;
        scanf("%d%d%d%d",&x,&y,&l,&r);
        printf("%d\n",(r-l+1)%2==0?0:f[x]^f[y]);
    }
    return 0;
}
  • 18
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值