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=1nj=i+1n[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)——树上启发式合并的主要内容,如果未能解决你的问题,请参考以下文章

Strange Memory Gym - 102832F

2020第6届中国大学生程序设计竞赛CCPC长春站, 签到题3题

2020第6届中国大学生程序设计竞赛CCPC长春站, 签到题3题

CCPC2016长春F (hdu 5916 Harmonic Value Description)

2017CCPC 长春

2020 CCPC 长春 J. Abstract Painting(状压dp)