[CF468D]Tree

Posted skylee的OI博客

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[CF468D]Tree相关的知识,希望对你有一定的参考价值。

[CF468D]Tree

题目大意:

? 一棵\(n(n\le10^5)\)个编号为\(1\sim n\)的点的带边权的树,求一个排列\(p_{1\sim n}\),使\(\sum dis(i,p_i)\)最大。求最大化的\(\sum dis(i,p_i)\)以及字典序最小的\(p\)

思路:

? 考虑第一问。用\(dis(x)\)表示点\(x\)到根的距离。则不难发现\(\sum dis(i,p_i)=\sum(dep_i+dep_{p_i}-2\times dep_{lca(i,p_i)})=2\times\sum dep_i-2\times\sum dep_{lca(i,p_i)}\)。而如果我们能够找到一个合适的点作为根,使得\(lca(i,p_i)=1\)则答案最大值即为\(2\times\sum dep_i\)。而通过证明可以发现一个点可以作为根当且仅当这个点是树的重心,证明如下(引自Code仓库):

\(P\)为重心,若\(P\)不可被当作公共点,设\(T_1\)\(P\)的大小\(>\lfloor\frac n2\rfloor\)的子树,其根为\(Q\),那么把\(Q\)拔掉的话,包含\(P\)的那棵子树的大小就会\(<n?\lfloor\frac n2\rfloor=\lceil\frac n2\rceil\le\lfloor\frac n2\rfloor+1\le T_1\)的大小,并且把\(Q\)拔掉后的其他子树大小显然都会小于\(T_1\)的大小,因此把\(Q\)拔掉会让剩余的最大子树的大小比把\(P\)拔掉的还要小,则\(P\)不是重心,矛盾。因此重心可以被当作公共点。

再来证明非重心的点不能被当作公共点,一样设\(P\)为重心,并且\(Q\)不是重心,他落在\(P\)\(T_1\)子树中,那么有\(T_1\)的大小\(\le\lfloor\frac n2\rfloor\),因此整棵树扣掉\(T_1\)的大小\(\ge\lceil\frac n2\rceil\),因此可以得到若\(Q\)想要当公共点,他就必须是\(T_1\)的根,并且满足\(T_1\)的大小刚好是\(\lfloor\frac n2\rfloor\),并且整棵树扣掉\(T_1\)的大小要刚好是\(\lceil\frac n2\rceil\),所以就可以得到\(n\)为偶数,\(T_1\)的大小为\(\frac n2\),所以\(Q\)也是重心,矛盾。

? 此时我们已经完成了第一问,可以解决HDU4118这个问题。现在考虑第二问,即如何求出字典序最小的\(p\)

? 如果定义排列中\(i\)为出点,\(p_i\)为入点,将树上的每一个点拆成一个入点和一个出点,那么题目就变成了一个完全匹配问题。去掉重心后原图分为\(T_{1\sim r}\)\(r\)个子树,记子树\(T_i\)中有\(in[i]\)个未匹配的入点,\(out[i]\)个未匹配的出点,显然初始状态\(in[i]=out[i]=size(T_i)\)。由于每个出点都要匹配不同一个子树的一个入点,则\(out[i]\le in[1]+\ldots+in[i-1]+in[i+1]+\ldots+in[r]\),即\(in[i]+out[i]\le\sum_{j=1}^r in[j]\),也即\(in[i]+out[i]\)小于此时未匹配的入点个数。若按\(1\sim n\)的顺序求\(p_i\),则对于每一时刻,对于每一棵子树\(T_j\),都有\(in[j]+out[j]\le n-i+1\)

? 若存在子树\(T_j\),满足\(in[j]+out[j]=n-i+1\),则\(p_i\)必须在\(T_j\)中取,因为要保证字典序最小,将\(T_j\)中最小的入点作为\(p_i\)即可。若不存在这样的\(T_j\),则可以从任意一个不同于\(i\)所属子树的子树中选取最小值。

? 这些最小值可以通过线段树、红黑树、二叉堆等数据结构来维护。考虑使用std::set(红黑树),用std::set<int> in[N]维护每个子树中所有未匹配的入点编号,std::set<int> min维护每个子树中未匹配的编号最小的入点,std::set<std::pair<int,int>> set记录每个子树中未匹配的入点和出点总数和该子树编号。时间复杂度\(\mathcal O(n\log n)\)

源代码:

#include<set>
#include<cstdio>
#include<cctype>
#include<climits>
#include<forward_list>
using int64=long long;
inline int getint() {
    register char ch;
    while(!isdigit(ch=getchar()));
    register int x=ch^'0';
    while(isdigit(ch=getchar())) x=(((x<<2)+x)<<1)+(ch^'0');
    return x;
}
constexpr int N=1e5+1;
using Edge=std::pair<int,int>;
std::forward_list<Edge> e[N];
inline void add_edge(const int &u,const int &v,const int &w) {
    e[u].push_front({v,w});
    e[v].push_front({u,w});
}
int n,m,size[N],par[N],bel[N],max[N]={INT_MAX},sum[N],cen;
int64 dis[N],ans;
void dfs(const int &x,const int &par) {
    size[x]=1;
    for(auto &j:e[x]) {
        const int &y=j.first;
        if(y==par) continue;
        dfs(y,x);
        size[x]+=size[y];
        max[x]=std::max(max[x],size[y]);
    }
    max[x]=std::max(max[x],n-size[x]);
    if(max[x]<max[cen]) cen=x;
}
void dfs(const int &x) {
    ans+=dis[x];
    for(auto &j:e[x]) {
        const int &y=j.first,&w=j.second;
        if(y==par[x]) continue;
        dis[y]=dis[par[y]=x]+w;
        dfs(y);
    }
}
std::set<int> in[N];
std::set<int> min;
std::set<std::pair<int,int>> set;
void work(const int &x,const int &root) {
    in[bel[x]=root].insert(x);
    for(auto &j:e[x]) {
        const int &y=j.first;
        if(y==par[x]) continue;
        work(y,root);
    }
}
inline void link(const int &x,const int &y) {
    const int &p=bel[x],&q=bel[y];
    min.erase(y);
    if(p) {
        set.erase({sum[p],p});
        set.insert({--sum[p],p});
    }
    if(q) { 
        in[q].erase(y);
        if(!in[q].empty()) min.insert(*in[q].begin());
        set.erase({sum[q],q});
        set.insert({--sum[q],q});
    }
}
inline int solve(const int &x) {
    int ret;
    if(set.rbegin()->first==n-x+1&&set.rbegin()->second!=bel[x]) {
        ret=*in[set.rbegin()->second].begin();
    } else {
        ret=bel[*min.begin()]!=bel[x]||x==cen?*min.begin():*std::next(min.begin());
    }
    link(x,ret);
    return ret;
}
int main() {
    for(register int i=n=getint();i>1;i--) {
        const int u=getint(),v=getint();
        add_edge(u,v,getint());
    }
    dfs(1,0);
    dfs(cen);
    printf("%lld\n",ans*2);
    if(n==1) {
        puts("1");
        return 0;
    }
    min.insert(cen);
    for(register auto &j:e[cen]) {
        const int &x=j.first;
        work(x,x);
        min.insert(*in[x].begin());
        set.insert({sum[x]=in[x].size()*2,x});
    }
    for(register int i=1;i<=n;i++) {
        printf("%d%c",solve(i)," \n"[i==n]);
    }
    return 0;
}

以上是关于[CF468D]Tree的主要内容,如果未能解决你的问题,请参考以下文章

CF468D Tree(树的重心)

[51nod][cf468D]1558 树中的配对

CF570D:Tree Requests

CF 570 D. Tree Requests

「CF911F」Tree Destruction

题解 CF375D Tree and Queries