「题解」「CF468D」树中的配对

本博客除代码之外,来自 skylee 大佬。

题目大意

一棵\(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 in[N]维护每个子树中所有未匹配的入点编号,std::set min维护每个子树中未匹配的编号最小的入点,std::set> set记录每个子树中未匹配的入点和出点总数和该子树编号。

时间复杂度\(\mathcal O(n\log n)\)

源代码

#include<cstdio>
#include<set>
#include<utility>
using namespace std;

#define rep(i,__l,__r) for(signed i=__l,i##_end_=__r;i<=i##_end_;++i)
#define fep(i,__l,__r) for(signed i=__l,i##_end_=__r;i>=i##_end_;--i)
#define writc(a,b) fwrit(a),putchar(b)
#define mp(a,b) make_pair(a,b)
#define ft first
#define sd second
#define LL long long
#define ull unsigned long long
#define uint unsigned int
#define pii pair< int,int >
#define Endl putchar('\n')
// #define FILEOI
// #define int long long
// #define int unsigned

#ifdef FILEOI
# define MAXBUFFERSIZE 500000
    inline char fgetc(){
        static char buf[MAXBUFFERSIZE+5],*p1=buf,*p2=buf;
        return p1==p2&&(p2=(p1=buf)+fread(buf,1,MAXBUFFERSIZE,stdin),p1==p2)?EOF:*p1++;
    }
# undef MAXBUFFERSIZE
# define cg (c=fgetc())
#else
# define cg (c=getchar())
#endif
template<class T>inline void qread(T& x){
    char c;bool f=0;
    while(cg<'0'||'9'<c)f|=(c=='-');
    for(x=(c^48);'0'<=cg&&c<='9';x=(x<<1)+(x<<3)+(c^48));
    if(f)x=-x;
}
inline int qread(){
    int x=0;char c;bool f=0;
    while(cg<'0'||'9'<c)f|=(c=='-');
    for(x=(c^48);'0'<=cg&&c<='9';x=(x<<1)+(x<<3)+(c^48));
    return f?-x:x;
}
// template<class T,class... Args>inline void qread(T& x,Args&... args){qread(x),qread(args...);}
template<class T>inline T Max(const T x,const T y){return x>y?x:y;}
template<class T>inline T Min(const T x,const T y){return x<y?x:y;}
template<class T>inline T fab(const T x){return x>0?x:-x;}
inline int gcd(const int a,const int b){return b?gcd(b,a%b):a;}
inline void getInv(int inv[],const int lim,const int MOD){
    inv[0]=inv[1]=1;for(int i=2;i<=lim;++i)inv[i]=1ll*inv[MOD%i]*(MOD-MOD/i)%MOD;
}
template<class T>void fwrit(const T x){
    if(x<0)return (void)(putchar('-'),fwrit(-x));
    if(x>9)fwrit(x/10);
    putchar(x%10^48);
}
inline LL mulMod(const LL a,const LL b,const LL mod){//long long multiplie_mod
    return ((a*b-(LL)((long double)a/mod*b+1e-8)*mod)%mod+mod)%mod;
}

const int MAXN=1e5;

struct edge{
    int to,nxt,w;
    edge(const int T=0,const int N=0,const int W=0):to(T),nxt(N),w(W){}
}e[(MAXN<<1)+5];
int tail[MAXN+5],ecnt;
inline void add_edge(const int u,const int v,const int w){
    e[++ecnt]=edge(v,tail[u],w);tail[u]=ecnt;
    e[++ecnt]=edge(u,tail[v],w);tail[v]=ecnt;
}

int n,siz[MAXN+5],fa[MAXN+5];
LL dis[MAXN+5],ans1;
int rt,tsize=(1<<30)-1;

inline void dfs(const int u,const int ff){
    siz[u]=1;
    int maxx=0;
    for(int i=tail[u],v;i;i=e[i].nxt)if((v=e[i].to)!=ff){
        dfs(v,u);
        siz[u]+=siz[v];
        maxx=Max(maxx,siz[v]);
    }
    maxx=Max(maxx,n-siz[u]);
    if(maxx<tsize)rt=u,tsize=maxx;
}

inline void dfs(const int u){
    ans1+=dis[u];
    for(int i=tail[u],v;i;i=e[i].nxt)if((v=e[i].to)!=fa[u]){
        dis[v]=dis[fa[v]=u]+e[i].w;
        dfs(v);
    }
}

int bel[MAXN+5];
set<int>in[MAXN+5];
//维护每一个子树的入点编号
set<int>minn;
//维护 每个子树合法入点的最小值 的最小值
//每个子树最多只会在 minn 中存在一个节点
set< pair<int,int> >Set;//维护 in[tre]+out[tre] 的最小值以及 tre 的值

inline void init_tre(const int u,const int top){//初始化每一颗子树
    in[bel[u]=top].insert(u);
    // printf("Now u == %d, bel[u] == %d\n",u,bel[u]);
    for(int i=tail[u],v;i;i=e[i].nxt)if((v=e[i].to)!=fa[u])
        init_tre(v,top);
}

inline void link(const int from,const int to){
    int x=bel[from],y=bel[to];
    minn.erase(to);
    //为了处理 rt 的 bel==0 的情况
    if(x){
        Set.erase(mp(siz[x],x));
        Set.insert(mp(--siz[x],x));
    }
    if(y){
        in[y].erase(to);
        if(!in[y].empty())minn.insert(*in[y].begin());
        Set.erase(mp(siz[y],y));
        Set.insert(mp(--siz[y],y));
    }
}

inline int solve(const int ind){
    int ret;
    if(Set.rbegin()->first==n-ind+1 && Set.rbegin()->second!=bel[ind])
        ret=*in[Set.rbegin()->second].begin();
    else
        ret=(bel[ind]!=bel[*minn.begin()] || ind==rt)?(*minn.begin()):(*next(minn.begin()));
        //如果当前点与最小入点不在同一颗子树或者当前点为根, 可直接选取最小入点, 否则要选择下一个
        //为什么直接是下一个即可 ? 因为每一颗子树在 minn 里面的点只会有一个, 可以保证 next 一定不是在同一个子树之内的
    link(ind,ret);
    return ret;
}

signed main(){
#ifdef FILEOI
    freopen("file.in","r",stdin);
    freopen("file.out","w",stdout);
#endif
    for(int i=n=qread(),u,v;i>1;--i){
        qread(u),qread(v);
        add_edge(u,v,qread());
    }
    dfs(1,0);//找到树的重心
    dfs(rt);//根据重心重新建树
    writc(ans1<<1,'\n');
    if(n==1)return puts("1"),0;

    minn.insert(rt);
    // in[rt].insert(rt);
    // bel[rt]=rt;
    // Set.insert(mp(siz[rt]=2,rt));
    //根也算一颗单独的子树
    //此处 Set 里面不能放 rt
    //因为 Set 里面维护的是 rt 的子树, 而 rt 本身并不是子树

    for(int i=tail[rt],v;i;i=e[i].nxt){//预处理每一颗子树
        v=e[i].to;
        init_tre(v,v);
        minn.insert(*in[v].begin());
        Set.insert(mp(siz[v]=(in[v].size()<<1),v));
    }//注意:siz[i] 从此处开始就变为了这个子树中 in+out 的值

    rep(i,1,n)writc(solve(i),' ');
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/Arextre/p/12222795.html