题目
思路
既然是二进制操作,就应该考虑拆位。现在我们只考虑二进制下的第 k ( k ≥ 0 ) k(k\ge 0) k(k≥0) 位,也就是代表 2 k 2^k 2k 的一位。
紧接着,我们让题目变的平凡,假设所有的 v x = 0 v_x=0 vx=0 。
现在我们回头去看题目中的定义式
⨁ c ∈ t r e e ( x ) [ v c + d ( c , x ) ] \bigoplus_{c\in tree(x)}\big[v_c+d(c,x)\big] c∈tree(x)⨁[vc+d(c,x)]
根据假设, v c = 0 v_c=0 vc=0 。既然我们只考虑第 k k k 位,我们就可以进行取模。对于第 k k k 位,我们只关心
⨁ c ∈ t r e e ( x ) [ d ( c , x ) m o d 2 k + 1 ] \bigoplus_{c\in tree(x)}\big[d(c,x)\bmod 2^{k+1}\big] c∈tree(x)⨁[d(c,x)mod2k+1]
正确性是显而易见的,因为 2 k + 1 2^{k+1} 2k+1 的倍数不需要使用第 k k k 或更低的二进制位来表示。 d d d 是距离,我们可以重新定义 d d d 为深度。而深度作差再取模,也可以 先取模再作差,变成
⨁ c ∈ t r e e ( x ) [ d ( c ) m o d 2 k + 1 − d ( x ) m o d 2 k + 1 ] \bigoplus_{c\in tree(x)}\Big[d(c)\bmod 2^{k+1}-d(x)\bmod 2^{k+1}\Big] c∈tree(x)⨁[d(c)mod2k+1−d(x)mod2k+1]
因为懒,不想把差值再打一个取模。
你惊奇地发现,我们只在乎深度取模后的结果。不难想到这样一个思路, f ( k , i ) f(k,i) f(k,i) 表示 深度 m o d 2 k + 1 = i \bmod\;2^{k+1}=i mod2k+1=i 的点有多少个。当然啦,因为是异或,也可以只存其奇偶性。
而这个差值需要是多少,才能在异或中改变第 k k k 位(即第 k k k 位为 1 1 1)呢?应当是 [ 2 k , 2 k + 1 ) [2^k,2^{k+1}) [2k,2k+1) 内。所以我们只需求
∑ i = 2 k 2 k + 1 − 1 f ( k , i + d ( x ) ) \sum_{i=2^k}^{2^{k+1}-1}f(k,i+d(x)) i=2k∑2k+1−1f(k,i+d(x))
因为懒,我又没有把二者的和打上取模。
事实上,求这个值的奇偶性就够了,为奇则第 k k k 位为 1 1 1 。不过这对解题没啥帮助。顶多让代码更好实现。
考虑这个 f f f 数组的空间占用。应该是 1 + 2 + 4 + 8 + ⋯ + n = O ( n ) 1+2+4+8+\cdots+n=\mathcal O(n) 1+2+4+8+⋯+n=O(n) 的。单次修改也是 O ( log n ) \mathcal O(\log n) O(logn) 的——对于每个 k k k 都要改。查询?好像是 O ( n ) \mathcal O(n) O(n) 的呢!
我们教练在考试时随便整了一个算法,用 B I T \tt BIT BIT 来维护 f f f ,以求高效区间求和。单次修改变为 O ( log 2 n ) \mathcal O(\log^2 n) O(log2n) 的,单次查询变成 O ( log 2 n ) \mathcal O(\log^2 n) O(log2n) 的。总复杂度 O ( n log 2 n ) \mathcal O(n\log^2 n) O(nlog2n) 。
看一看题解,找到了更优的做法,重拾树形 d p \tt dp dp ,利用子树的信息更新父亲。
为啥这样想?因为二者的深度差是 1 1 1 呢。你的儿子已经求出
∑ i = 2 k 2 k + 1 − 1 f ( k , i + d ( y ) ) \sum_{i=2^k}^{2^{k+1}-1}f(k,i+d(y)) i=2k∑2k+1−1f(k,i+d(y))
而 i + d ( y ) = i + d ( x ) + 1 i+d(y)=i+d(x)+1 i+d(y)=i+d(x)+1 ,于是上式等价于(注意 i i i 的取值范围的变化):
∑ i = 2 k + 1 2 k + 1 f ( k , i + d ( x ) ) \sum_{i=2^k+1}^{2^{k+1}}f(k,i+d(x)) i=2k+1∑2k+1f(k,i+d(x))
于是我们找差值部分,无非是 i = 2 k i=2^k i=2k 和 i = 2 k + 1 i=2^{k+1} i=2k+1 两个端点,一个加一个减就行。当然,还得把自己加上。单次查询复杂度降级为 O ( log n ) \mathcal O(\log n) O(logn) 。修改复杂度不变。
亲爱的读者,你有没有忘记我们规定了 v = 0 v=0 v=0 呢?对于 v ≠ 0 v\ne 0 v=0 怎么处理?
在 x x x 的子树处理完之后,假装 v x + d ( x ) v_x+d(x) vx+d(x) 处有一个 v = 0 v=0 v=0 。因为 x x x 的子树已搞定,对于接下来的计算,与 x x x 处有一个 v x v_x vx 是等价的。
复杂度 O ( n log n ) \mathcal O(n\log n) O(nlogn) 。如果 f f f 使用定长数组,就是 O ( n log n ) \mathcal O(n\log n) O(nlogn) 的空间复杂度。
我的口胡听上去完美,实际上有缺陷,那就是 f f f 使用后不清空,会对兄弟子树产生影响。用 d s u o n t r e e \tt dsu\; on\; tree dsuontree 解决,就要平添一个 log n \log n logn 。不可接受!
考虑到这道题的特殊性,用 log n \log n logn 个元素就能唯一确定当前点的取值。你会兴奋地想到,只清空这 log n \log n logn 的值就行了!这个想法完全正确。按照字面意思实现就会失败,你清空,你的子孙也把这几个值清零怎么办?正确的方法是 记录变化量,这相当于重设了原点、实现了清空。
在本题中,可以直接先异或原有的值,再异或现有的值。这些都是为了代码好写而已。你非要把代码写的又臭又长也没人拦你。
代码
小坑点:最大异或值可能达到 v + d = 2 n v+d=2n v+d=2n 级别,所以 log n \log n logn 要再加一。
#include <cstdio>
#include <iostream>
#include <vector>
#include <algorithm>
#include <cstring>
using namespace std;
typedef long long int_;
inline int readint() {
int a = 0; char c = getchar(), f = 1;
for(; c<'0' or c>'9'; c=getchar())
if(c == '-') f = -f;
for(; '0'<=c and c<='9'; c=getchar())
a = (a<<3)+(a<<1)+(c^48);
return a*f;
}
void writeint(int_ x){
if(x < 0) putchar('-'), x = -x;
if(x > 9) writeint(x/10);
putchar((x%10)^48);
}
# define MB template < typename T >
MB void getMax(T &a,const T &b){ if(a < b) a = b; }
MB void getMin(T &a,const T &b){ if(b < a) a = b; }
const int MaxN = 525011;
struct Edge{
int to, nxt;
Edge(int T=0,int N=0){
to = T, nxt = N;
}
} edge[MaxN<<1];
int head[MaxN], cntEdge;
void addEdge(int a,int b){
edge[cntEdge] = Edge(b,head[a]);
head[a] = cntEdge ++;
}
const int LogN = 21; // 注意!最大可达 v+d=2n
int cnt[LogN][2<<LogN], v[MaxN];
long long ans; // 递归顺便统计答案
/** @param d 当前点的深度 */
int dfs(int x,int d){
int now = v[x]; // 当前点的答案
/* 记录变化量,一定要先于递归 */
/* (这一段与下面的那一段是相同的) */
for(int i=0; i<LogN; ++i)
now ^= (cnt[i][(d+(1<<i))%(2<<i)]
^cnt[i][d%(2<<i)])<<i;
/* 递归,并且利用好它们的信息 */
for(int i=head[x]; ~i; i=edge[i].nxt)
now ^= dfs(edge[i].to,d+1);
for(int i=0; i<LogN; ++i){
/* 加上 d+2^{i} 的情况 */
if(cnt[i][(d+(1<<i))%(2<<i)])
now ^= 1<<i;
/* 减去 d+2^{i+1}=d 的情况 */
if(cnt[i][d%(2<<i)])
now ^= 1<<i;
/**
* 为啥加和减长得一样?
* 因为我们只求奇偶性。
**/
}
/* 假装 d+v[x] 深度的地方有一个 v=0 */
for(int i=0; i<LogN; ++i) // %(2<<i)
cnt[i][(d+v[x])%(2<<i)] ^= 1;
ans += now; return now;
}
int main(){
int n = readint();
for(int i=1; i<=n; ++i){
v[i] = readint();
head[i] = -1;
}
for(int i=2; i<=n; ++i)
addEdge(readint(),i);
dfs(1,0); printf("%lld\n",ans);
return 0;
}
后记
本来都过去很久了,突然又想起这道题,是因为机房里的一个妹儿 z x y \tt zxy zxy 做了这道题。
我作为一个男生,肯定不能服输!虽然还是被她吊着打。
话说洛咕题解里也有很近似的做法,但是都称其为树上差分……?果然我图论一点都不会,我只会代数。在考场上我连代数都不会,我只会痴呆。