本博客除代码之外,来自 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. ubuntu 16.04.1上安装并使用vsftpd

    1.安装vsftpd软件 sudo apt-get install vsftpd 2.新建文件 sudo vim /etc/vsftpd.user_list 用于记录用户名 3. 修改sudo vim ...

  2. UI中class的用法:

    easyui的引入:<link rel="stylesheet" type="text/css" href="easyui/themes/def ...

  3. 在阿里云IIS服务器上安装证书

    在IIS服务器上安装证书 您可将下载的阿里云SSL证书安装到IIS服务器上,使您的IIS服务器支持HTTPS安全访问. 一.前提条件 申请证书时需要选择 系统自动创建CSR. 申请证书时如果选择手动创 ...

  4. 用Navicat连接mysql报错:2003-Can't connect to MySql server on '10.100.0.109'(10039)

    问题描述 在 window 系统上,用 Navicat 连接 Mysql 数据库,弹出以下提示 问题原因  Mysql 数据库未启动 解决方案:启动 Mysql 数据库 1.点击桌面左下角运行 cmd ...

  5. webpack如何编译ES6打包

    前言:随着ES的普及我们越来越多的开始使用ES6的语法了,当然也随着mvvm框架的流行少不了js模块化,那js模块化又有那些呢 在很早的时候大家都用的命名空间,现在也有人用(库名.类别名.方法名) 后 ...

  6. 【vue】 vue跳转页面:router-link/this.$router.push()

    1.通过标签<router-link> <router-link to='A'>跳转到A页面</router-link> 2.通过方法 this.$router.p ...

  7. 二分-B - Dating with girls(1)

    B - Dating with girls(1) Everyone in the HDU knows that the number of boys is larger than the number ...

  8. promise的连缀写法

    promise的连缀写法 以上写法相当于写了两个实例 promise.all() 1. promise.all() all这个方法是 promise 构造函数的成员不是实例对象成员,这个方法接受一个参 ...

  9. JS高级---遍历DOM树

    遍历DOM树  第一个函数: 给我根节点, 我会找到所有的子节点: forDOM(根节点)  获取这个根节点的子节点  var children=根节点的.children  调用第二个函数  第二个 ...

  10. rest_framework:响应器(渲染器)

    一.作用: 根据用户的请求url或者用户可接受的类型.筛选出合适的渲染组件 用户请求url: http://127.0.0.1:8000/test/?format=json http://127.0. ...