首页 > 其他 > 详细

树上启发式合并_训练总结+题目清单

时间:2020-06-10 15:23:22      阅读:43      评论:0      收藏:0      [点我收藏+]

前言:树上启发式合并(DSU on Tree),是一个在O(nlogn) 时间内解决许多树上问题的有力算法,其对于树上离线问题的处理速度大于等于其他的算法,且更容易理解(个人认为处理与子树的关系牵涉很多)。具体思路大概就是先像树链剖分那样找到每个结点的重儿子,然后把所有轻儿子的贡献合并于重儿子(比较抽象吧~),当前结点操作完毕之后,再看如果当前结点是其父亲结点的一个轻儿子,那么该轻儿子贡献全部置0。对于重儿子的贡献保留。至于时间复杂度为什么是O(logn),我也不太会证明,但是肯定比在线暴力对每个儿子操作其全部结点快,因为首先Dsu On Tree是个离线算法,你每次对当前结点的操作,实际是只要处理轻儿子结点(重儿子结点贡献已经有了),这么来看你就较常规方法少了再遍历重儿子的那一步,时间复杂度自然而然就降了。

技术分享图片←大概就是这个样子吧OvO

 

 

 我觉得理解一个算法的,重要的一步是训练刷题+总结,所以我附上一些刷题题单,前面几个题会简单一些(CF2300分左右),最后一个题有点难(CF2900分)

技术分享图片

 

 接下来我一道题一道题地写一下解析叭:

A:Codeforces-600E(Lomsat Gelral)

题意:对于每个子树,输出子树中出现次数最多的节点编号之和。(次数最多的编号有多个节点都要统计进去)

解法:这是一道比较模板的树上启发式合并题,每一次count,统计当前颜色col[u]出现的次数即cnt[col[u]]。如果其次数大于所有颜色中出现次数最多颜色的大小maxc,即把maxc更新成当前结点的颜色大小cnt[col[u]],同时更新sum。另外该题要求如果子树中出现颜色次数一样,那么和就等于这些相同颜色值大小和,所以你还需要特判更新一波。

技术分享图片
#include<bits/stdc++.h>
#define ll long long
#define rep(i,a,n) for(int i=a;i<=n;i++)
#define per(i,n,a) for(int i=n;i>=a;i--)
#define endl ‘\n‘
#define mem(a,b) memset(a,b,sizeof(a))
#define IO ios::sync_with_stdio(false);cin.tie(0);
using namespace std;
const int INF=0x3f3f3f3f;
const ll inf=0x3f3f3f3f3f3f3f3f;
const int mod=1e9+7;
const int maxn=1e5+5;
int tot,head[maxn];
struct E{
    int to,next;
}edge[maxn<<1];
void add(int u,int v){
    edge[tot].to=v;
    edge[tot].next=head[u];
    head[u]=tot++;
}
int col[maxn],cnt[maxn];
int siz[maxn],son[maxn];
void dfs(int u,int f){
    siz[u]=1;
    for(int i=head[u];i!=-1;i=edge[i].next){
        int v=edge[i].to;
        if(v==f) continue;
        dfs(v,u);
        siz[u]+=siz[v];
        if(siz[v]>siz[son[u]]){
            son[u]=v;
        }
    }
}
ll ans[maxn],sum;
int flag,maxc;
void count(int u,int f,int val){
    cnt[col[u]]+=val;
    if(cnt[col[u]]>maxc){
        maxc=cnt[col[u]];
        sum=col[u];
    }
    else if(cnt[col[u]]==maxc)
        sum+=col[u];
    for(int i=head[u];i!=-1;i=edge[i].next){
        int v=edge[i].to;
        if(v==f||v==flag) continue;
        count(v,u,val);
    }    
}
void dfs(int u,int f,bool keep){
    for(int i=head[u];i!=-1;i=edge[i].next){
        int v=edge[i].to;
        if(v==f||v==son[u]) continue;
        dfs(v,u,false);
    }
    if(son[u]){
        dfs(son[u],u,true);
        flag=son[u];
    }
    count(u,f,1);
    flag=0;
    ans[u]=sum;
    if(!keep){
        count(u,f,-1);
        sum=maxc=0;
    }
}
int main(){
    int n;scanf("%d",&n);
    mem(head,-1);
    rep(i,1,n) scanf("%d",&col[i]);
    rep(i,1,n-1){
        int u,v;scanf("%d%d",&u,&v);
        add(u,v);add(v,u);
    }
    dfs(1,0);
    dfs(1,0,0);
    rep(i,1,n) printf("%lld ",ans[i]);
    puts("");
}
View Code

B:Codeforces-246E(Blood Cousins Return) 

题意:给定一棵树,已知每个节点的父亲节点,和每个节点上的名字(字符串),对于每个询问vi,ki,求子树vi内v的kth层儿子(子树内比子树根深度大k层的部分)中不重复名字的个数。

解法:因为有可能是森林,所以你需要统计一共有多少个树rootcnt,并记录每个数的根节点root[i](判断其是否为某个数根节点只需要看它父亲是不是0就行啦),利用map离散化一下,就把名字当成颜色好了。同时count的时候也要注意某子树如果当前结点为u当前层中该颜色即col[u]是否在其他子树出现过,即也用map来记录一下,如果没有那ok,如果有的话,当前层次的颜色你就不需要再更新啦!另外值得一提的是:对于结点询问,如果只给你某个结点的v和第几层d,那么可以用vector<pair<int,int>> q[maxn]来记录,第一个q[u]记录结点v,q[u][i].first记录询问的id,q[u][i].second记录层次。然后你统计当前询问的时候,只需要看当前结点u是否充当v的时候q[u].size()是否>0,若如此则说明有询问坐落于该结点上。

技术分享图片
#include<bits/stdc++.h>
#define ll long long
#define rep(i,a,n) for(int i=a;i<=n;i++)
#define per(i,n,a) for(int i=n;i>=a;i--)
#define endl ‘\n‘
#define eps 0.000000001
#define mem(a,b) memset(a,b,sizeof(a))
#define IO ios::sync_with_stdio(false);cin.tie(0);
using namespace std;
const int INF=0x3f3f3f3f;
const ll inf=0x3f3f3f3f3f3f3f3f;
const int mod=1e9+7;
const int maxn=1e5+5;
int tot,head[maxn];
struct E{
    int to,next;
}edge[maxn<<1];
void add(int u,int v){
    edge[tot].to=v;
    edge[tot].next=head[u];
    head[u]=tot++;
}
int n;map<string,int> mp;int c[maxn],root[maxn];
vector<pair<int,int> >q[maxn];
int siz[maxn],son[maxn],dep[maxn];
void dfs1(int u,int f){
    dep[u]=dep[f]+1;
    siz[u]=1;
    for(int i=head[u];i!=-1;i=edge[i].next){
        int v=edge[i].to;
        if(v==f) continue;
        dfs1(v,u);
        siz[u]+=siz[v];
        if(siz[v]>siz[son[u]]) son[u]=v;
    }
}
int flag,ans[maxn];
int cnt[maxn];
map<pair<int,int>,int> pk;
void count(int u,int f,int val){
    if(val==1&&!pk[{dep[u],c[u]}]) cnt[dep[u]]+=val,pk[{dep[u],c[u]}]+=val;
    if(val==-1&&pk[{dep[u],c[u]}]) cnt[dep[u]]+=val,pk[{dep[u],c[u]}]+=val;
    for(int i=head[u];i!=-1;i=edge[i].next){
        int v=edge[i].to;
        if(v==f||v==flag) continue;
        count(v,u,val);
    }
}
void dfs(int u,int f,int keep){
    for(int i=head[u];i!=-1;i=edge[i].next){
        int v=edge[i].to;
        if(v==f||v==son[u]) continue;
        dfs(v,u,0);
    }
    if(son[u]){
        dfs(son[u],u,1);
        flag=son[u];
    }
    count(u,f,1);
    for(int i=0;i<q[u].size();i++){
        int id=q[u][i].first;
        int d=q[u][i].second;
        ans[id]=cnt[dep[u]+d];
    }
    flag=0;
    if(!keep){
        count(u,f,-1);        
    }
}
int main(){
    cin>>n;mem(head,-1);
    int cntcolor=0;int cntroot=0;
    rep(i,1,n){
        string s;int x;cin>>s>>x;
        if(!mp[s]) mp[s]=++cntcolor;
        c[i]=mp[s];
        if(!x){
            ++cntroot;
            root[cntroot]=i;
        }else{
            add(x,i);add(i,x);
        } 
    }
    int m;cin>>m;
    rep(i,1,m){
        int u,d;cin>>u>>d;
        q[u].push_back({i,d});
    }
    rep(i,1,cntroot){
        dfs1(root[i],0);
    }
    rep(i,1,cntroot){
        dfs(root[i],0,0);
    }
    rep(i,1,m){
        cout<<ans[i]<<endl;
    }
}
View Code

 

树上启发式合并_训练总结+题目清单

原文:https://www.cnblogs.com/Anonytt/p/13085122.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!