题意:给你一棵树,每个节点有编号1~n。求一个字典序最小的排列满足sigma(i到p[i]的距离)最大。
n<=1e5.
标程:
1 #include<bits/stdc++.h>
2 #define P pair<int,int>
3 #define fir first
4 #define sec second
5 using namespace std;
6 typedef long long ll;
7 const int N=100005;
8 int cnt,head[N],Max[N],size[N],rt,Ans[N],num[N],n,u,v,w,blo,anc[N];
9 ll ans,dis[N];
10 set<int> s[N];
11 set<P> SZ,T;
12 struct node{int to,next,w;}Num[N*2];
13 void add(int x,int y,int w)
14 {Num[++cnt].to=y;Num[cnt].next=head[x];Num[cnt].w=w;head[x]=cnt;}
15 void find_rt(int x,int fa)
16 {
17 size[x]=1;
18 for (int i=head[x];i;i=Num[i].next)
19 if (Num[i].to!=fa)
20 {
21 find_rt(Num[i].to,x);
22 size[x]+=size[Num[i].to];
23 Max[x]=max(Max[x],size[Num[i].to]);
24 }
25 Max[x]=max(Max[x],n-size[x]);
26 if (Max[rt]>Max[x]) rt=x;
27 }
28 void dfs(int x,int fa,int Anc)
29 {
30 if (Anc) anc[x]=Anc,s[Anc].insert(x);ans+=2*dis[x];
31 for (int i=head[x];i;i=Num[i].next)
32 if (Num[i].to!=fa)
33 {
34 dis[Num[i].to]=dis[x]+Num[i].w;
35 if (!Anc) dfs(Num[i].to,x,++blo);else dfs(Num[i].to,x,Anc);
36 }
37 }
38 void link(int x,int y)
39 {
40 int p=*s[y].begin();
41 SZ.erase(P(num[y],y));SZ.erase(P(num[anc[x]],anc[x]));
42 T.erase(P(p,y));
43 Ans[x]=p;s[y].erase(p);
44 if ((int)s[y].size()) T.insert(P(*s[y].begin(),y));
45 num[anc[x]]--;num[y]--;
46 SZ.insert(P(num[y],y));SZ.insert(P(num[anc[x]],anc[x]));
47 }
48 int main()
49 {
50 scanf("%d",&n);
51 for (int i=1;i<n;i++) scanf("%d%d%d",&u,&v,&w),add(u,v,w),add(v,u,w);
52 Max[rt=0]=n+1;find_rt(1,-1);
53 dfs(rt,0,0);
54 s[0].insert(rt);
55 for (int i=0;i<=blo;i++)
56 T.insert(P(*s[i].begin(),i)),num[i]=2*(int)s[i].size(),SZ.insert(P(num[i],i));
57 for (int i=1;i<=n;i++)//顺次枚举要匹配的点
58 {
59 if (SZ.rbegin()->fir==n-i+1&&SZ.rbegin()->sec!=anc[i]&&SZ.rbegin()->sec!=0) link(i,SZ.rbegin()->sec);
60 else {
61 set<P>::iterator now=T.begin();
62 if (i!=rt&&now->sec==anc[i]) ++now;
63 link(i,now->sec);
64 }
65 }
66 printf("%lld\n",ans);
67 for (int i=1;i<=n;i++) printf("%d%c",Ans[i],(i==n)?10:32);
68 return 0;
69 }
易错点:1.要开ll。
2.SZ.rbegin()->sec!=0这句话不加得出的解也是对的,但CF上说错。。。
题解:树的重心+set+技巧
Ans=sigma(dis[i]+dis[p[i]]-2dis[lca(i,p[i])])=2*sigma(dis[i])-2*sigma(dis[lca(i,p[i])])。
即要使得sigma(dis[lca(i,p[i])])最小。如果lca(i,p[i])都是根答案就是2*sigma(dis[i])。
以重心为根,肯定能够构造出解。
考虑字典序最小。顺次枚举要匹配的i,如果直接找不在同一棵子树中的最小编号匹配,最后有可能出现不得不在同一棵树里的情况。
统计从根裂开的每一棵子树中 编号>=i的点的个数+未被匹配的点的个数=num。每次最多会从num中取走一个点(取走两个就在同一棵子树中了)。当num=n-i+1时,则每一次必然都在该子树中选择一个。则该子树必选。反之,挑选除i所在子树外最小的一个。
s[i]表示第i棵子树的未匹配点集。T表示每棵子树中选出的最小编号点集。SZ表示每棵子树num的集合,动态维护最大值。
来源:https://www.cnblogs.com/Scx117/p/9072761.html