本博客除代码之外,来自 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. k8s部署k8s-dashboard(v2.0.0-rc5)

    部署dashboard 版本问题 dashboard版本更新换代很快,而且每个版本对应的k8s版本都有可能不同,所以第一步要确定版本对应关系. 查看页面可以确认版本对应关系 版本对应不上可能出现很多语 ...

  2. Mac 多版本 JDK 管理

    Mac 多版本 JDK 管理 1. 准备 ZSH Homebrew Oracle JDK 1.8 安装包(Homebrew 官方源和第三方源不再提供老版本的 Oracle JDK) 2. 安装 JDK ...

  3. nginx配置从远程获取静态资源

    前置条件:现有两台内网互通机器192.168.0.100.192.168.0.101,其中192.168.0.100可以通过外网网络.业务需求:需要通过外网访问处于192.168.0.101机器上的静 ...

  4. MyEclipse-2017破解过程

    下载 myeclipse2017百度云下载路径: 链接:https://pan.baidu.com/s/1wQYwO2zrUvbbUcjCB5B8IQ 密码:6igu myeclipse2017破解文 ...

  5. dataTables插件的使用

    用到dataTables这个插件还是因为Metronic这个框架里有用到,不然我不会选择它的,为啥呢?就感觉它的文档有点复杂(当然,也有我智商不够用的原因):既然用了,那就说说我遇到的问题吧,以防下次 ...

  6. int*v=newint[src.cols*4]

    在学习:使用OpenCV2.x计算图像的水平和垂直积分投影中,有下图一种代码: 对比上面两个代码对于同一张图片求得的结果会发现不同: 为什么会出现这个原因呢?不知道为啥这样初始化? 首先查看一下图片深 ...

  7. BibTex 学习笔记

    BibTex 学习笔记 使用BibTex 来管理参考文献:一次管理,终身使用! 1. 定义 BibTeX 是一个使用数据库的的方式来管理参考文献程序, 用于协调LaTeX的参考文献处理. BibTeX ...

  8. svn checkout使用

    (1)需要知道三个东西:svn路径和号码和密码. (2)svn导出的地方建立需要的目录,然后在不同目录下svn check out,输入正确的路径和号码和密码,这样就可以正常svn下来目录了.

  9. HTTP状态码的浪漫故事

    小明是一个程序员,经常加班,在一个和尚部门,一个妹子都没有,所以一直单身. 404(Not Found):服务器无法根据客户端的请求找到资源(网页) 过年回家,老爸老妈给门开一条缝,先看看小明身后-- ...

  10. windows下划分逻辑分区

    运行命令窗口后,我们输入命令"diskpart"回车! 然后我们在DISKPART>后面输入select disk 0 选择我们的硬盘,然后回车!当然如果你电脑上有好几块硬盘 ...