F. Strange Memory
给出一个n个点n-1条边的一颗树 (n<=1e5)
对于一个点$i$有值$a_i$ (1<=ai<=1e6)
现在要求 $∑{i=1}^n∑ {j=i+1}^n [a_i⊕a_j=a_{lca(i,j)}]×(i⊕j). $
即对于任何满足$[a_i⊕a_j=a_{lca(i,j)}]$ 的点对(i,j) 的下标异或值求和
参考题解
dsu on tree
解决这个问题需要解决以下几个问题:
如何找到符合$[a_i⊕a_j=a_{lca(i,j)}]$ 的点对
如何统计答案
对于第一个问题 我们可以枚举一个点作为Lca 同时枚举一个它(Lca)的子节点作为$i$
那么只需要寻找以它(Lca)为根的子节点中是否存在一个$a_j=a_{Lca} ⊕ a_{i}$
对于第二个问题
朴素的想法是 我们用一个容器(比如map<int,vector >,关键字是点值,映射值是该点值的所有点下标值)
存某个点作为Lca的一支子树中 值为$a_j$的点的下标
于是对于这个Lca需要枚举另一支子树中的 $a_i$
再枚举遍历容器中值为($a_{Lca} ⊕ a_{i}$) 的所有下标值 ans+=i⊕j
然后将$a_i$所在子树插入到容器中传递到上一层
这么做导致 我们不得不遍历子树中的$a_i$ 和$a_j$ 来获取下标的异或值
实际上就是在暴力枚举点对(i,j)没有任何优化
###于是要着手如何更快速地统计答案;
首先意识到i⊕j 可以拆分成按位异或
那么当且仅当 符合$[a_i⊕a_j=a_{lca(i,j)}]$ 的点对(i,j) 在同一位二进制下相异
最终答案才会有这一位二进制的贡献
比如点对(3,6) 二进制下(011,110) 3^6=5 即第0位和第2位相异的二进制位会对最终答案产生贡献
如果我们知道Lca下某个子树集合内 $a_j$的所有下标每位二进制为0/1的个数有几个
那么我们枚举$a_i$ 时可以直接算 对于需要的$a_j=a_i ⊕a_{Lca}$ 中所有二进制位 为0/1的下标有多少个
就可以直接求得某位二进制下 i⊕j对答案产生的贡献
于是设计一个三维数组:$BitCnt(val,bit,0/1)$
表示在某Lca的子树下点值为val的 下标 符合第bit位 为0/1 共有多少个
子树中$a_j$被作为一个集合统计在$BitCnt$中 就不需要逐个枚举$a_j$ 而改为枚举二进制位了
这么做的目的也是为了方便使用树上启发式合并
这个问题中需要更新维护每个点作为Lca时的BitCnt 也就是对子树集合信息的询问
对于dsu on tree的算法流程大概是:
首先需要预处理重链剖分(FInd_BigSon) 得到对于每个父节点 它子节点相对而言的重子树
什么叫消除轻儿子产生的影响而保留重儿子?
以本题为例 我们需要维护$BitCnt$ 是以某个点作为Lca的子树集合信息
而他作为全局变量 它在递归中被以不同点作为root的子树集合信息更新
而我们在求点对(i,j)时 是同一个点Lca 下的子树集合中 的信息
如图 (红圈表示以其父节点下所有子节点相较而言的重子树)
对于Lca=5的子树集合 i=6 我只需要从5的其他支子树的BitCnt集合中得到
而递归过程会将所有其他子树集合信息一并更新到BitCnt这个全局变量中 导致对于i=6
我所找到对应的j 就不仅限于Lca=5的子树中的信息了
对于启发式合并 每次将轻子树集合的信息并入到重子树中
抹除轻子树的信息 然后将重子树传递到上一层递归 (返回到父节点)
若上一层的父节点不再是重子树 那么抹除它们的信息 将他们视为轻子树集合
重新并入到同一层中的重子树中
这也体现了启发式合并的核心思想:
将小集合信息并入到大集合信息使得整体复杂度下降到nlogn 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 #include <bits/stdc++.h> using namespace std ;typedef long long ll;#define endl '\n' #define Turnoff std::ios::sync_with_stdio(false) const int Max=1e5 +5 ;const int Mod=1e9 +7 ;const int inf=0x3f3f3f3f ;#ifdef __DEBUG__ #define DeBug(format, ...) { fprintf (stdout , "[%s:%d] " format "" , __FUNCTION__ , __LINE__, ##__VA_ARGS__); } #else #define DeBug(format,...) #endif int BitCnt[(1 <<20 )+100 ][30 ][2 ];vector <int >mp[Max];queue <int >SmallSon;int val[Max];ll ans=0 ; int sz[Max],BigSon[Max];bool vis[Max];void Find_BigSon (int now,int fa) { sz[now]=1 ; for (auto to:mp[now]){ if (to==fa)continue ; Find_BigSon(to,now); sz[now]+=sz[to]; } if (sz[now]>sz[BigSon[fa]])BigSon[fa]=now; } inline void Updata (int id,int add) { DeBug("id:%d add:%d\n" ,id,add); for (int i=0 ;i<=20 ;i++)BitCnt[val[id]][i][!((id>>i)&1 )]+=add; } void Delete (int now,int fa) { Updata(now,-1 ); for (auto to:mp[now]){ if (to==fa)continue ; Delete(to,now); } } void Deal (int now,int fa,int lca) { SmallSon.push(now); int need=val[now]^val[lca]; for (int i=0 ;i<=20 ;i++){ ans+=BitCnt[need][i][(now>>i)&1 ]*(1 <<i); } for (auto to:mp[now]){ if (to==fa)continue ; Deal(to,now,lca); } } void dfs (int now,int fa,bool keep) { for (auto to:mp[now]){ if (to==fa||to==BigSon[now])continue ; dfs(to,now,0 ); } if (BigSon[now])dfs(BigSon[now],now,1 ); vis[BigSon[now]]=1 ; Updata(now,1 ); for (auto to:mp[now]){ if (to==fa||vis[to])continue ; Deal(to,now,now); while (!SmallSon.empty()){ int temp=SmallSon.front(); SmallSon.pop(); Updata(temp,1 ); } } vis[BigSon[now]]=0 ; if (!keep)Delete(now,fa); } int main (void ) { int n;cin >>n; for (int i=1 ;i<=n;i++)cin >>val[i]; for (int i=1 ;i<n;i++){ int u,v;cin >>u>>v; mp[u].push_back(v); mp[v].push_back(u); } Find_BigSon(1 ,0 ); dfs(1 ,0 ,1 ); cout <<ans<<endl ; }