【51NOD 1558】树中的配对

本文探讨了在一个给定的树形结构中寻找一个排列,使得各节点与其配对节点间距离之和最大。通过使用树的重心概念,实现了一种高效算法来解决此问题,并给出了解决方案的具体代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Description

小X有一棵树,有n个结点(结点从1到n编号)。每一条边的长度是正的。d(v,u)表示点u,v之间的最短距离。
一个排列p是一个包含n个不同数字的序列 p1, p2, …, pn (1 ≤ pi ≤ n) 。
小X想找到一个排列,使得 ∑ni=1d(i,pi) 尽可能大。如果有多个排列符合要求,输出字典序最小的。

Solution

先想一下只求最大的距离ans怎么求,
一条边的最大计算次数肯定是它两侧连着的两堆点中个数的min,再*2;

再来考虑一下怎么求出d数组,
看到边两侧的min,就可以考虑树的重心,因为以重心为根,它的所有子树中包含的点的个数都不会超过n/2,
换句话说,就是它的每一个儿子,都必须先走到根,再走到其他子树中去,这样才可能是最大的方案,

那么就贪心的做,注意不能出现子树内于自己子树内的点匹配(重心可以自己匹配自己);这个用线段树维护一下即可。

复杂度:O(nlog(n))

Code

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <algorithm>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define efo(i,q) for(int i=A[q];i;i=B[i][0])
using namespace std;
typedef long long LL;
const int N=150500;
int read(int &n)
{
    char ch=' ';int q=0,w=1;
    for(;(ch!='-')&&((ch<'0')||(ch>'9'));ch=getchar());
    if(ch=='-')w=-1,ch=getchar();
    for(;ch>='0' && ch<='9';ch=getchar())q=q*10+ch-48;n=q*w;return n;
}
int m,n;
int a[N],si[N],g[N];
int B[4*N][3],A[N],B0,A1[N];
int b1[N];
LL ans;
int c[N],c1[N*3];
struct qqww
{
    int mi,mi1;
}b[N*3];
qqww min(qqww q,qqww w)
{
    if(q.mi>w.mi)q.mi1=q.mi,q.mi=w.mi;
        else q.mi1=min(q.mi1,w.mi);
    q.mi1=min(q.mi1,w.mi1);
    return q;
}
void link(int q,int w,int e)
{
    B[++B0][0]=A[q];A[q]=B0,B[B0][1]=w,B[B0][2]=e;
    B[++B0][0]=A[w];A[w]=B0,B[B0][1]=q,B[B0][2]=e;
}
int ZX,ZX1;
void dfsz(int q,int fa)
{
    int mx=0;
    si[q]=1;
    efo(i,q)if(B[i][1]!=fa)
    {
        dfsz(B[i][1],q);si[q]+=si[B[i][1]];
        mx=max(mx,si[B[i][1]]);ans+=(LL)min(n-si[B[i][1]],si[B[i][1]])*(LL)B[i][2];
    }
    mx=max(mx,n-si[q]);
    if(ZX1>mx)ZX=q,ZX1=mx;
}
void dfs(int q,int fa)
{
    b1[++b1[0]]=q;
    efo(i,q)if(B[i][1]!=fa)dfs(B[i][1],q);
}
void build(int l,int r,int e)
{
    if(l==r)
    {
        c1[e]=c[l],b[e].mi=B[A1[l]][0],b[e].mi1=1e9;
        A1[l]++;
        return;
    }
    int t=(l+r)>>1;
    build(l,t,e*2),build(t+1,r,e*2+1);
    c1[e]=max(c1[e*2],c1[e*2+1]);
    b[e]=min(b[e*2],b[e*2+1]);
}
int find(int l,int r,int e,int l1,int l2)
{
    if(l==r)return b[e].mi;
    int t=(l+r)>>1;
    if(c1[e]>l1)
    {
        if(c1[e*2]>c1[e*2+1])return find(l,t,e*2,l1,l2);
        return find(t+1,r,e*2+1,l1,l2);
    }
    if(g[b[e].mi]==l2&&l2!=m)return b[e].mi1;
    return b[e].mi;
}
void change(int l,int r,int e,int l1,bool KD)
{
    if(l==r)
    {
        c1[e]--;
        if(KD)
        {
            if(B[A1[l]][0])
            {
                b[e].mi=B[A1[l]][0];
                A1[l]++;
            }else b[e].mi=1e9;
        }
        return;
    }
    int t=(l+r)>>1;
    if(l1<=t)change(l,t,e*2,l1,KD);
        else change(t+1,r,e*2+1,l1,KD);
    c1[e]=max(c1[e*2],c1[e*2+1]);
    b[e]=min(b[e*2],b[e*2+1]);
}
int main()
{
    int q,w,e;
    read(n);
    fo(i,1,n-1)read(q),read(w),read(e),link(q,w,e);
    ZX1=1e9,dfsz(1,0);
    printf("%lld\n",ans*2);
    m=0;
    B0++;
    efo(i,ZX)
    {
        m++;b1[0]=0;
        dfs(B[i][1],ZX);
        sort(b1+1,b1+1+b1[0]);
        c[m]=2*b1[0];
        A1[m]=++B0;
        fo(j,1,b1[0])B[B0++][0]=b1[j],g[b1[j]]=m;
    }
    A1[++m]=++B0;
    B[B0++][0]=ZX,g[ZX]=m,c[m]=1;
    build(1,m,1);
    fo(i,1,n)
    {
        change(1,m,1,g[i],0);
        q=find(1,m,1,n-i,g[i]);
        printf("%d ",q);
        change(1,m,1,g[q],1);
    }
    return 0;
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值