题意:给你一棵树,每个节点有编号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的集合,动态维护最大值。