本博客除代码之外,来自 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;
}

「题解」「CF468D」树中的配对的更多相关文章

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

    http://www.51nod.com/onlineJudge/questionCode.html#!problemId=1558 不是很懂dalao们用线段树是怎么写的…… 反正找出重心以后每个子 ...

  2. 「ZJOI2019」&「十二省联考 2019」题解索引

    「ZJOI2019」&「十二省联考 2019」题解索引 「ZJOI2019」 「ZJOI2019」线段树 「ZJOI2019」Minimax 搜索 「十二省联考 2019」 「十二省联考 20 ...

  3. 「题解」「美团 CodeM 资格赛」跳格子

    目录 「题解」「美团 CodeM 资格赛」跳格子 题目描述 考场思路 思路分析及正解代码 「题解」「美团 CodeM 资格赛」跳格子 今天真的考自闭了... \(T1\) 花了 \(2h\) 都没有搞 ...

  4. 「题解」「HNOI2013」切糕

    文章目录 「题解」「HNOI2013」切糕 题目描述 思路分析及代码 题目分析 题解及代码 「题解」「HNOI2013」切糕 题目描述 点这里 思路分析及代码 题目分析 这道题的题目可以说得上是史上最 ...

  5. 「题解」JOIOI 王国

    「题解」JOIOI 王国 题目描述 考场思考 正解 题目描述 点这里 考场思考 因为时间不太够了,直接一上来就着手暴力.但是本人太菜,居然暴力爆 000 ,然后当场自闭- 一气之下,发现对 60pts ...

  6. 【题解】「P6832」[Cnoi2020]子弦

    [题解]「P6832」[Cnoi2020]子弦第一次写月赛题解( 首先第一眼看到这题,怎么感觉要用 \(\texttt{SAM}\) 什么高科技的?结果一仔细读题,简单模拟即可. 我们不难想出,出现最 ...

  7. 「题解报告」 P3167 [CQOI2014]通配符匹配

    「题解报告」 P3167 [CQOI2014]通配符匹配 思路 *和?显然无法直接匹配,但是可以发现「通配符个数不超过 \(10\) 」,那么我们可以考虑分段匹配. 我们首先把原字符串分成多个以一个通 ...

  8. 「kuangbin带你飞」专题十七 AC自动机

    layout: post title: 「kuangbin带你飞」专题十七 AC自动机 author: "luowentaoaa" catalog: true tags: - ku ...

  9. 「bzoj1003」「ZJOI2006」物流运输 最短路+区间dp

    「bzoj1003」「ZJOI2006」物流运输---------------------------------------------------------------------------- ...

  10. 「bzoj1925」「Sdoi2010」地精部落 (计数型dp)

    「bzoj1925」「Sdoi2010」地精部落---------------------------------------------------------------------------- ...

随机推荐

  1. Python 高维数组“稀疏矩阵”scipy sparse学习笔记

    scipy 里面的sparse函数进行的矩阵存储 可以节省内存 主要是scipy包里面的 sparse 这里目前只用到两个 稀疏矩阵的读取 sparse.load() 转稀疏矩阵为普通矩阵 spars ...

  2. VSCode常用插件之open in browser使用

    更多VSCode插件使用请访问:VSCode常用插件汇总 open in browser安装完这个插件就可以在编辑器菜单右键html,在默认浏览器打开了,高级使用暂未了解,请自行其它文章学习

  3. vue.js 的cdn 链接的引用地址

    引用地址有两种一种完整版,一种压缩版效果是一样的 https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js https://cdn.bootcss.com ...

  4. 小程序 与 App 与 H5 之间的区别

    小程序的实现原理 根据微信官方的说明,微信小程序的运行环境有 3 个平台,iOS 的 WebKit(苹果开源的浏览器内核),Android 的 X5 (QQ 浏览器内核),开发时用的 nw.js(C+ ...

  5. [CF1034A] Two Rabbits - 数学

    判断能否整除即可 #include <bits/stdc++.h> using namespace std; int x,y,a,b; int main() { int t; ios::s ...

  6. C++——字符串处理

    11.用字符数组存储和处理字符串 字符数组的声明和引用  字符串: 字符串常量 “china”,没有字符串变量,用字符数组来存放字符串,字符串以‘\0’结束. 字符串数组的初始化: 逐个输出输入字符串 ...

  7. asm相关内容想下载(包括 jar 包)

    网址:http://download.forge.ow2.org/asm/

  8. 微信小程序中的左右联动

    微信小程序端的左右联动-滚动效果插件: 效果图如下:                                                                          ...

  9. 巨杉学习笔记 | SequoiaDB MySQL导入导出工具使用实战

    本文来自社区用户投稿,感谢这位小伙伴的技术分享 巨杉数据库架构简介 巨杉数据库作为分布式数据库是计算和存储分离架构,由数据库实例层和存储引擎层组成的.存储引擎层负责数据库核心功能比如数据读写存储以及分 ...

  10. C++——绪论

    计算机语言的发展 1.机器语言(二进制).汇编语言.比较难以理解和识记,与人类语言之间的差距太大: 2.高级语言,可以写出类似于人类思维的语句,可以有人们习惯的表达方式: 3.面向对象的语言,描述客观 ...