Codeforces 600E Lomsat gelral

Codeforces 600E Lomsat gelral

树上启发式合并

题意

一棵树,每一个点有一个颜色,统计以每一个节点为根的子树中出现次数最多的颜色的编号,如果有多个颜色,统计他们的和。

思路

学习了一下dsu on the tree,安利一下良心博客1博客2,以及CF官方解答
算法的大致过程:
首先将树轻重链剖分,在dfs的过程中先dfs轻儿子,再dfs重儿子。
假设某一个点的儿子都已经被dfs过,统计这个点的答案。统计答案的过程中要calc当前这个点的子树,但是只calc它的轻儿子,重儿子不做。
这样的话,就需要在dfs的过程中,如果当前点是它父亲的轻儿子,做完这个点之后就将影响暴力消除;而如果这个点是它父亲的重儿子,则将这个点的影响保留。
因为重儿子比较大,所以最后统计重儿子,并保留重儿子信息,可以降低复杂度。总体复杂度 O(nlogn) ,很优秀。

另外有 O(nlog2n) 的平衡树合并,需要启发式合并。
mp[n][k]表示某节点n及其子树中,颜色k出现的次数。sum[n][k]表示某节点n及其子树中,出现k次的颜色的和。

代码

dsu on tree

#include<bits\stdc++.h>
#define M(a,b) memset(a,b,sizeof(a))
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
using namespace std;
const int MAXN=100007;
typedef long long LL;
struct Edge
{
    int to, ne;
}e[MAXN<<1];
int head[MAXN], edgenum, val[MAXN];
inline void addedge(int u, int v)
{
    e[edgenum].to=v, e[edgenum].ne=head[u], head[u]=edgenum++;
    e[edgenum].to=u, e[edgenum].ne=head[v], head[v]=edgenum++;
}
int hson[MAXN], sz[MAXN];
void dfs1(int n, int fa)
{
    sz[n]=1;hson[n]=0;
    for(int i=head[n];~i;i=e[i].ne)
    {
        int to=e[i].to;
        if(to!=fa)
        {
            dfs1(to, n);
            if(!hson[n]) hson[n]=to;
            else if(sz[hson[n]]<sz[to]) hson[n]=to;
            sz[n]+=sz[to];
        }
    }
}
LL res, ans[MAXN];
int mval, cnt[MAXN], hs;
void suan(int n, int fa, int v)
{
    cnt[val[n]]+=v;
    if(mval<cnt[val[n]]) res=(LL)val[n], mval=cnt[val[n]];
    else if(mval==cnt[val[n]]) res+=(LL)val[n];
    for(int i=head[n];~i;i=e[i].ne)
        if(e[i].to!=fa&&e[i].to!=hs)
            suan(e[i].to, n, v);
}
void dfs2(int n, int fa, int kep)
{
    for(int i=head[n];~i;i=e[i].ne)
    {
        int to=e[i].to;
        if(to!=fa&&to!=hson[n])
            dfs2(to, n, 0);
    }
    if(hson[n]) dfs2(hson[n], n, 1), hs=hson[n];
    suan(n, fa, 1);hs=0;
    ans[n]=res;
    if(!kep) suan(n, fa, -1), mval=res=0;
}
int main()
{
    int n;
    while(scanf("%d", &n)==1)
    {
        for(int i=1;i<=n;i++)
            scanf("%d", &val[i]);
        M(head, -1);edgenum=0;M(ans, 0);
        for(int i=1;i<n;i++)
        {
            int t1, t2;scanf("%d%d", &t1, &t2);
            addedge(t1, t2);
        }
        dfs1(1, -1);
        dfs2(1, -1, 1);
        for(int i=1;i<=n;i++)
            printf("%lld%c", ans[i], i==n ? '\n' : ' ');
    }
    //system("pause");
    return 0;
}

平衡树启发式合并

#include<bits\stdc++.h>
#define M(a,b) memset(a,b,sizeof(a))
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
using namespace std;
const int MAXN=100007;
typedef long long LL;
map<int, int> mp[MAXN];
map<int, LL> sum[MAXN];
struct Edge
{
    int to, ne;
}e[MAXN<<1];
int head[MAXN], edgenum;
int col[MAXN];
LL ans[MAXN];
void dfs(int n, int fa)
{
    mp[n].clear();sum[n].clear();
    mp[n][col[n]]=1;sum[n][1]=col[n];
    for(int i=head[n];~i;i=e[i].ne)
    {
        int to=e[i].to;
        if(to!=fa)
        {
            dfs(to, n);
            if(mp[n].size()<mp[to].size())
                swap(mp[n], mp[to]), swap(sum[n], sum[to]);//启发式
            for(map<int, int>::iterator ite=mp[to].begin();ite!=mp[to].end();ite++)
            {
                sum[n][mp[n][ite->first]]-=ite->first;
                mp[n][ite->first]+=ite->second;
                sum[n][mp[n][ite->first]]+=ite->first;
            }
        }
    }
    ans[n]=sum[n].rbegin()->second;
}
int main()
{
    int n;
    while(scanf("%d", &n)==1)
    {
        M(head, -1), edgenum=0;
        for(int i=1;i<=n;i++) scanf("%d", &col[i]);
        for(int i=1;i<n;i++)
        {
            int a, b;scanf("%d%d", &a, &b);
            e[edgenum].to=b, e[edgenum].ne=head[a], head[a]=edgenum++;
            e[edgenum].to=a, e[edgenum].ne=head[b], head[b]=edgenum++;
        }
        dfs(1, -1);
        for(int i=1;i<=n;i++)
        {
            printf("%lld%c", ans[i], i==n ? '\n' : ' ');
        }
    }
    //system("pause");
    return 0;
}
close
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值