← 回到所有文章

P8965 坠梦 | Falling into Dream 题解

0x00 mathcal{O}(n^3) 预处理 mathcal{O}(n) 查询 这种大量查询的题目很容易想到的是预处理出来每一对 operatorname{dis}(x, y) 。这样在查询的时候…

题解文章的简约插画封面

0x00 O(n3)\mathcal{O}(n^3) 预处理 O(n)\mathcal{O}(n) 查询

这种大量查询的题目很容易想到的是预处理出来每一对 dis⁡(x,y)\operatorname{dis}(x, y) 。这样在查询的时候就可以 O(n)O(n) 查询出来,并且求出答案。具体实现可以这样做:由于“ dis⁡(x,y)\operatorname{dis}(x, y) 为 x,yx,y 两点之间唯一简单路径上边权的异或和。”很显然的是,对这个无根树选定一个根节点,这条简单路径还是原来的简单路径,那么 O(1)O(1) 求出 x,yx,y 的 lca 节点,暴力跑一遍路径进行计算即可。

0x01 O(n2)\mathcal{O}(n^2) 预处理 O(n)\mathcal{O}(n) 查询

对于 0x00 部分的算法,可以进行优化,首先预处理把每一个对 dis⁡(x,y)\operatorname{dis}(x, y) 时间复杂度不会变化,那么 O(1)\mathcal{O}(1) 求出 x,yx,y 的 lca 节点后,希望可以 O(1)\mathcal{O}(1) 求出 dis⁡(x,y)\operatorname{dis}(x, y) ,考虑这里 dis⁡(x,y)\operatorname{dis}(x, y) 若不是“路径上边权的异或和”而是“路径上边权的和”,可以使用树上前缀和,那么异或和也可以采用这样的方法,处理出每一个节点到根节点的异或和,参见下面一个图:

对于 dis⁡(4,5)\operatorname{dis}(4, 5) 已知 dis⁡(1,4)=1⊕3\operatorname{dis}(1, 4) = 1 \oplus 3 和 dis⁡(1,5)=1⊕4\operatorname{dis}(1, 5) = 1 \oplus 4 这两个信息重复的部分即为 dis⁡(1,2)=1\operatorname{dis}(1, 2) = 1 ,但是由于异或的自反性质,直接讲这两部分异或起来就去除了 dis⁡(1,2)=1\operatorname{dis}(1, 2) = 1 这部分,即为 dis⁡(1,5)⊕dis⁡(1,2)=3⊕4⊕1⊕1=3⊕4\operatorname{dis}(1, 5) \oplus \operatorname{dis}(1, 2) = 3 \oplus 4 \oplus 1 \oplus 1 = 3 \oplus 4 。那么就可以做到 “O(n2)\mathcal{O}(n^2) 预处理 O(n)\mathcal{O}(n) 查询” 。

0x02 O(n)\mathcal{O}(n) 预处理 O(1)\mathcal{O}(1) 查询

刚才的算法打开了一个很好的思路就是利用了异或的自反性,希望还可以利用更多这样的性质,这时整体考虑

val⁡x,y(i)=dis⁡(x,i)⊕dis⁡(y,i)=dis⁡(x,1)⊕dis⁡(i,1)⊕dis⁡(y,1)⊕dis⁡(i,1)=dis⁡(x,1)⊕dis⁡(y,1)\operatorname{val}_{x, y}(i) = \operatorname{dis}(x, i) \oplus \operatorname{dis}(y, i)\\ = \operatorname{dis}(x, 1) \oplus \operatorname{dis}(i, 1) \oplus \operatorname{dis}(y, 1) \oplus \operatorname{dis}(i, 1) \\ = \operatorname{dis}(x, 1) \oplus \operatorname{dis}(y, 1)

可以发现 val⁡x,y(i)\operatorname{val}_{x, y}(i) 和 ii 没关系,那么对于 ⨁i=lrval⁡x,y(i)\displaystyle \bigoplus_{i = l}^{r} \operatorname{val}_{x, y}(i) 也就是 ⨁i=lrdis⁡(x,1)⊕dis⁡(y,1)\displaystyle \bigoplus_{i = l}^{r} \operatorname{dis}(x, 1) \oplus \operatorname{dis}(y, 1) ,相当于自己和自己异或了 r−l+1r-l+1 次,更具自反性质得到 r−l+1 mod 2=1r-l+1\bmod 2 = 1 时为 dis⁡(x,1)⊕dis⁡(y,1)\operatorname{dis}(x, 1) \oplus \operatorname{dis}(y, 1) ,否则为 00 。

#include <cstdio>
#include <algorithm>
#include <queue>
#define _min(a,b) ((a)<(b)?(a):(b))
#define _max(a,b) ((a)>(b)?(a):(b))
struct edge{
	int to,nxt,w;
	edge(){
		nxt=-1;
		return;
	} 
}tree[1000010<<1];
int tot,head[1000010];
long long V_pre[1000010];

void addEdge(int u,int v,int w){
	tree[tot].nxt=head[u];
    tree[tot].w=w;
	tree[tot].to=v;
	head[u]=tot;
	++tot;
	return;
}

int read(){
	long long x=0;char f=0,c=getchar();
	while(c<'0'||'9'<c) f|=(c=='-'),c=getchar();
	while('0'<=c&&c<='9') x=(x<<1)+(x<<3)+(c&15),c=getchar();
	return f?-x:x; 
}

void dfs(int u,int p,int w){
	V_pre[u]=V_pre[p]^w;
	for(int i=head[u];~i;i=tree[i].nxt){
		if(tree[i].to!=p)
			dfs(tree[i].to,u,tree[i].w);
	}
}

int main(){
	register int i,u,v,w,x,y,l,r;
    register long long ans;
	int n=read();
	int Q=read();

    for(i=1;i<=n;++i) head[i]=-1;

	for(i=1;i<n;++i){
		u=read();
		v=read();
        w=read();
		addEdge(u,v,w);
		addEdge(v,u,w);
	}

	dfs(1,1,0);

	for(i=0;i<Q;++i){
		x=read();
        y=read();
        l=read();
        r=read();
        ans=(((r-l+1)&1)?(V_pre[x]^V_pre[y]):0);
        printf("%lld",ans);
        putchar('\n');
	}

	return 0;
}

谢谢你,读到这里。

本文同步自洛谷专栏,保留原始正文与发布时间。在洛谷查看原文 ↗

搜索文章

输入关键词,发现一些值得阅读的文字。

探索文字里的小小世界esc 关闭