CF468D Tree

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

猜你喜欢

转载自www.cnblogs.com/Scx117/p/9072761.html