CCPC20长春F(gym102832F)——树上启发式合并
Posted hans774882968
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了CCPC20长春F(gym102832F)——树上启发式合并相关的知识,希望对你有一定的参考价值。
题意
对一个有点权的树,求以下公式的值:
∑
i
=
1
n
∑
j
=
i
+
1
n
[
a
[
i
]
x
o
r
a
[
j
]
=
a
[
l
c
a
(
i
,
j
)
]
]
∗
(
i
x
o
r
j
)
\\sum_{i=1}^{n}\\sum_{j=i+1}^n [a[i] \\ xor \\ a[j] = a[lca(i,j)]] * (i\\ xor \\ j)
i=1∑nj=i+1∑n[a[i] xor a[j]=a[lca(i,j)]]∗(i xor j)
a数组的值是1~1e6。
思路
从点编号大小关系来考虑,没有任何收获。于是我们从子树的角度来考虑:以1为根的子树(整棵树)的任意两点的贡献和,就是所求。因此我们定义dp[u]
为以u
为子树的任意两点的贡献和,dp[1]
就是答案。dp转移分两部分:子树内部的贡献,以及子树间的贡献(其中子树其他点与a[u]
之间没有贡献,因为题目给定a[i]>0
)。子树内部贡献就是dp[v]
,那么只需要求子树间的贡献。
于是我们想出这样的计算方式:t
号子树和**1~t-1
号子树的信息之和的贡献,全部加起来。信息怎么求和?我们很容易想到每个子树都维护一个map,mp[a[u]]
表示一个vector,存该子树a
权值(a
权值是一个词,抱歉词穷了)为a[u]
的所有点编号。然后因为子树没有顺序规定,所以可以用启发式合并**来优化map的合并过程。但是这样做,只能n^2log统计信息,会TLE on test case 8。
我们再看一下式子结构,i ^ j
的外面是求和,这种异或只在内部出现的结构是可以直接拆位,算位贡献之和的。于是每个点编号就变成了0或1,此时统计子树间贡献,就可以用乘法了。复杂度nlog^3n
,会TLE on test case 5。但还是贴出代码,供参考。
实际上我们离正解已经不远了!map合并稍作修改,就能变成dsu on tree的解法!定义数据结构cnt[a[u]][0 or 1]
为子树中,a
权值为a[u]
,点编号在该位为0或1的点的个数。按照dsu on tree的合并顺序来合并+统计答案(即dp[u]
)即可。复杂度nlog^2n
。
这里有一个小技巧,预处理dfs序,于是我们不再需要写很多个遍历子树的dfs,简化了代码。大概长这样:
rep(i,dfn[v][0],dfn[v][1]){
const int p = seq[i];
//p是树的节点,下面是具体操作
}
坑点:cnt要开2^20的大小,而不是1e6,否则会wa on test case 3。
代码
nlog^3n
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef unordered_map<int,pair<int,int> > MP;
#define rep(i,a,b) for(int i = (a);i <= (b);++i)
#define re_(i,a,b) for(int i = (a);i < (b);++i)
#define dwn(i,a,b) for(int i = (a);i >= (b);--i)
const int N = 1e5 + 5;
int n,a[N],val[N];LL dp[N];
int son[N],siz[N];
vector<int> G[N];
void dbg(){puts("");}
template<typename T, typename... R>void dbg(const T &f, const R &... r) {
cout << f << " ";
dbg(r...);
}
template<typename Type>inline void read(Type &xx){
Type f = 1;char ch;xx = 0;
for(ch = getchar();ch < '0' || ch > '9';ch = getchar()) if(ch == '-') f = -1;
for(;ch >= '0' && ch <= '9';ch = getchar()) xx = xx * 10 + ch - '0';
xx *= f;
}
MP dfs2(int u,int ufa);
void merge(MP &mp,int v,int u);
set<int> dfs1(int u,int ufa){
son[u] = 0;int mx = 0;
set<int> ans = {a[u]};
for(int &v: G[u]){
if(v == ufa) continue;
set<int> nxt = dfs1(v,u);
for(int x: nxt) ans.insert(x);
if(siz[v] > mx) son[u] = v,mx = siz[v];
}
siz[u] = ans.size();
return ans;
}
void merge(MP &mp,int v,int u){
MP nxt = dfs2(v,u);
vector<pair<int,pair<int,int> > > tmp;
for(auto x: nxt) tmp.push_back({x.first,x.second});
re_(i,0,tmp.size()){
pair<int,pair<int,int> > &x = tmp[i];
pair<int,int> &p = mp[x.first^a[u]];
dp[u] += 1LL * x.second.first * p.second + 1LL * x.second.second * p.first;
}
re_(i,0,tmp.size()){
pair<int,pair<int,int> > &x = tmp[i];
int num0 = x.second.first,num1 = x.second.second;
if(num0) mp[x.first].first += num0;
if(num1) mp[x.first].second += num1;
}
dp[u] += dp[v];
}
MP dfs2(int u,int ufa){
MP mp;mp[a[u]] = {val[u] == 0,val[u] == 1};
if(son[u]) merge(mp,son[u],u);
for(int v: G[u]){
if(v == ufa || v == son[u]) continue;
merge(mp,v,u);
}
return mp;
}
int main(int argc, char** argv) {
read(n);
rep(i,1,n) read(a[i]);
re_(i,1,n){
int u,v;read(u);read(v);
G[u].push_back(v);G[v].push_back(u);
}
dfs1(1,0);
LL ans = 0;
for(int i = 0;(1 << i) <= n;++i){
rep(j,1,n) val[j] = j>>i&1,dp[j] = 0;
dfs2(1,0);
ans += dp[1] * (1 << i);
}
printf("%lld\\n",ans);
return 0;
}
正解
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
#define rep(i,a,b) for(int i = (a);i <= (b);++i)
#define re_(i,a,b) for(int i = (a);i < (b);++i)
#define dwn(i,a,b) for(int i = (a);i >= (b);--i)
const int N = 1e5 + 5;
int n,a[N],val[N];LL dp[N];
int son[N],siz[N],dfs_clk = 0,dfn[N][2],seq[N];int cnt[(1<<20)+5][2];
vector<int> G[N];
void dbg(){puts("");}
template<typename T, typename... R>void dbg(const T &f, const R &... r) {
cout << f << " ";
dbg(r...);
}
template<typename Type>inline void read(Type &xx){
Type f = 1;char ch;xx = 0;
for(ch = getchar();ch < '0' || ch > '9';ch = getchar()) if(ch == '-') f = -1;
for(;ch >= '0' && ch <= '9';ch = getchar()) xx = xx * 10 + ch - '0';
xx *= f;
}
void dfs1(int u,int ufa){
dfn[u][0] = ++dfs_clk;
seq[dfs_clk] = u;
siz[u] = 1;son[u] = 0;int mx = 0;
for(int v: G[u]){
if(v == ufa) continue;
dfs1(v,u);
if(siz[v] > mx) son[u] = v,mx = siz[v];
siz[u] += siz[v];
}
dfn[u][1] = dfs_clk;
}
void clr(int u){
rep(i,dfn[u][0],dfn[u][1]){
const int p = seq[i];
cnt[a[p]][val[p]] = 0;
}
}
void dfs2(int u,int ufa,bool keep = false){
for(int v: G[u]){
if(v == ufa || v == son[u]) continue;
dfs2(v,u,false);
dp[u] += dp[v];
}
if(son[u]){
dfs2(son[u],u,true);
dp[u] += dp[son[u]];
}
for(int v: G[u]){
if(v == ufa || v == son[u]) continue;
rep(i,dfn[v][0],dfn[v][1]){
const int p = seq[i];
dp[u] += cnt[a[p]^a[u]][!val[p]];
}
rep(i,dfn[v][0],dfn[v][1]){
const int p = seq[i];
++cnt[a[p]][val[p]];
}
}
++cnt[a[u]][val[u]];
if(!keep) clr(u);
}
int main(int argc, char** argv) {
read(n);
rep(i,1,n) read(a[i]);
re_(i,1,n){
int u,v;read(u);read(v);
G[u].push_back(v);G[v].push_back(u);
}
dfs1(1,0);
LL ans = 0;
以上是关于CCPC20长春F(gym102832F)——树上启发式合并的主要内容,如果未能解决你的问题,请参考以下文章
2020第6届中国大学生程序设计竞赛CCPC长春站, 签到题3题
2020第6届中国大学生程序设计竞赛CCPC长春站, 签到题3题