[CF468D]Tree
题目大意:
一棵(n(nle10^5))个编号为(1sim n)的点的带边权的树,求一个排列(p_{1sim 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 imes dep_{lca(i,p_i)})=2 imessum dep_i-2 imessum dep_{lca(i,p_i)})。而如果我们能够找到一个合适的点作为根,使得(lca(i,p_i)=1)则答案最大值即为(2 imessum dep_i)。而通过证明可以发现一个点可以作为根当且仅当这个点是树的重心,证明如下(引自Code仓库):
设(P)为重心,若(P)不可被当作公共点,设(T_1)是(P)的大小(>lfloorfrac n2 floor)的子树,其根为(Q),那么把(Q)拔掉的话,包含(P)的那棵子树的大小就会(<n−lfloorfrac n2 floor=lceilfrac n2 ceillelfloorfrac n2 floor+1le T_1)的大小,并且把(Q)拔掉后的其他子树大小显然都会小于(T_1)的大小,因此把(Q)拔掉会让剩余的最大子树的大小比把(P)拔掉的还要小,则(P)不是重心,矛盾。因此重心可以被当作公共点。
再来证明非重心的点不能被当作公共点,一样设(P)为重心,并且(Q)不是重心,他落在(P)的(T_1)子树中,那么有(T_1)的大小(lelfloorfrac n2 floor),因此整棵树扣掉(T_1)的大小(gelceilfrac n2 ceil),因此可以得到若(Q)想要当公共点,他就必须是(T_1)的根,并且满足(T_1)的大小刚好是(lfloorfrac n2 floor),并且整棵树扣掉(T_1)的大小要刚好是(lceilfrac n2 ceil),所以就可以得到(n)为偶数,(T_1)的大小为(frac n2),所以(Q)也是重心,矛盾。
此时我们已经完成了第一问,可以解决HDU4118这个问题。现在考虑第二问,即如何求出字典序最小的(p)。
如果定义排列中(i)为出点,(p_i)为入点,将树上的每一个点拆成一个入点和一个出点,那么题目就变成了一个完全匹配问题。去掉重心后原图分为(T_{1sim 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]lesum_{j=1}^r in[j]),也即(in[i]+out[i])小于此时未匹配的入点个数。若按(1sim 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<int> in[N]
维护每个子树中所有未匹配的入点编号,std::set<int> min
维护每个子树中未匹配的编号最小的入点,std::set<std::pair<int,int>> set
记录每个子树中未匹配的入点和出点总数和该子树编号。时间复杂度(mathcal O(nlog n))。
源代码:
#include<set>
#include<cstdio>
#include<cctype>
#include<climits>
#include<forward_list>
using int64=long long;
inline int getint() {
register char ch;
while(!isdigit(ch=getchar()));
register int x=ch^'0';
while(isdigit(ch=getchar())) x=(((x<<2)+x)<<1)+(ch^'0');
return x;
}
constexpr int N=1e5+1;
using Edge=std::pair<int,int>;
std::forward_list<Edge> e[N];
inline void add_edge(const int &u,const int &v,const int &w) {
e[u].push_front({v,w});
e[v].push_front({u,w});
}
int n,m,size[N],par[N],bel[N],max[N]={INT_MAX},sum[N],cen;
int64 dis[N],ans;
void dfs(const int &x,const int &par) {
size[x]=1;
for(auto &j:e[x]) {
const int &y=j.first;
if(y==par) continue;
dfs(y,x);
size[x]+=size[y];
max[x]=std::max(max[x],size[y]);
}
max[x]=std::max(max[x],n-size[x]);
if(max[x]<max[cen]) cen=x;
}
void dfs(const int &x) {
ans+=dis[x];
for(auto &j:e[x]) {
const int &y=j.first,&w=j.second;
if(y==par[x]) continue;
dis[y]=dis[par[y]=x]+w;
dfs(y);
}
}
std::set<int> in[N];
std::set<int> min;
std::set<std::pair<int,int>> set;
void work(const int &x,const int &root) {
in[bel[x]=root].insert(x);
for(auto &j:e[x]) {
const int &y=j.first;
if(y==par[x]) continue;
work(y,root);
}
}
inline void link(const int &x,const int &y) {
const int &p=bel[x],&q=bel[y];
min.erase(y);
if(p) {
set.erase({sum[p],p});
set.insert({--sum[p],p});
}
if(q) {
in[q].erase(y);
if(!in[q].empty()) min.insert(*in[q].begin());
set.erase({sum[q],q});
set.insert({--sum[q],q});
}
}
inline int solve(const int &x) {
int ret;
if(set.rbegin()->first==n-x+1&&set.rbegin()->second!=bel[x]) {
ret=*in[set.rbegin()->second].begin();
} else {
ret=bel[*min.begin()]!=bel[x]||x==cen?*min.begin():*std::next(min.begin());
}
link(x,ret);
return ret;
}
int main() {
for(register int i=n=getint();i>1;i--) {
const int u=getint(),v=getint();
add_edge(u,v,getint());
}
dfs(1,0);
dfs(cen);
printf("%lld
",ans*2);
if(n==1) {
puts("1");
return 0;
}
min.insert(cen);
for(register auto &j:e[cen]) {
const int &x=j.first;
work(x,x);
min.insert(*in[x].begin());
set.insert({sum[x]=in[x].size()*2,x});
}
for(register int i=1;i<=n;i++) {
printf("%d%c",solve(i),"
"[i==n]);
}
return 0;
}