【LOJ6042】「雅礼集训 2017 Day7」跳蚤王国的宰相(思博题)
大致题意: 给你一棵树,询问对于每个点需要改变多少条边来使得它成为树中到所有点距离和最小的点。
一些初始化及想法
这是一道思博题。
首先我们要知道一个结论:对于这棵树的重心,它的答案必定为\(0\)。
然后对于非重心的点该怎么办呢?
我们考虑把重心作为根,并统计出每个子节点的\(Size\)。
接下来我们可以发现,如果割掉根节点的若干棵子树,且这些子树\(Size\)和\(\ge\frac n2\),那么肯定就可以构造出一种合法的方案使得任意节点符合条件。
由于要割的次数最少,因此我们将根节点的子节点按\(Size\)从大到小排序,然后取尽量少的节点使得\(Size\)和\(\ge\frac n2\),并记录所需节点数为\(p\)。
答案的取值
对于除重心外的每个点,其答案只可能为\(p\)或者\(p-1\)。
什么时候能够取\(p-1\)呢?
假设一个点\(x\)位于根节点的子节点排序后的第\(i\)个子节点的子树内。
对于\(i\le p\),我们割去除\(i\)外第\(1\sim p\)个点到根节点的连边。如果这些被割去的子树的\(Size\)和加上\(Size_x\ge\frac n2\),那么我们就不需要再割一条新边了。
而对于\(i>p\)的情况也是类似的,只不过一开始割去的是第\(1\sim p-1\)个点到根节点的连边。
按照这样的方式,我们就可以求出答案了。
代码
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 1000000
#define INF 1e9
#define add(x,y) (e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y)
#define max(x,y) ((x)>(y)?(x):(y))
#define Gmax(x,y) (x<(y)&&(x=(y)))
using namespace std;
int n,p,rt,cnt,ee,s[N+5],lnk[N+5],Sz[N+5],Mx[N+5],ans[N+5];struct edge {int to,nxt;}e[N<<1];
class FastIO
{
private:
#define FS 100000
#define tc() (A==B&&(B=(A=FI)+fread(FI,1,FS,stdin),A==B)?EOF:*A++)
#define pc(c) (C^FS?FO[C++]=c:(fwrite(FO,1,C,stdout),FO[(C=0)++]=c))
#define tn (x<<3)+(x<<1)
#define D isdigit(c=tc())
int T,C;char c,*A,*B,FI[FS],FO[FS],S[FS];
public:
I FastIO() {A=B=FI;}
Tp I void read(Ty& x) {x=0;W(!D);W(x=tn+(c&15),D);}
Tp I void write(Ty x) {W(S[++T]=x%10+48,x/=10);W(T) pc(S[T--]);}
Ts I void read(Ty& x,Ar&... y) {read(x),read(y...);}
Tp I void writeln(Con Ty& x) {write(x),pc('\n');}
I void clear() {fwrite(FO,1,C,stdout),C=0;}
}F;
I void GetRt(CI x,CI lst=0)//求重心
{
for(RI i=(Sz[x]=1,Mx[x]=0,lnk[x]);i;i=e[i].nxt) e[i].to^lst&&
(GetRt(e[i].to,x),Sz[x]+=Sz[e[i].to],Gmax(Mx[x],Sz[e[i].to]));
Gmax(Mx[x],n-Sz[x]),Mx[x]<Mx[rt]&&(rt=x);
}
I void Init(CI x,CI lst=0)//初始化Size
{
for(RI i=(Sz[x]=1,lnk[x]);i;i=e[i].nxt)
e[i].to^lst&&(Init(e[i].to,x),Sz[x]+=Sz[e[i].to]);
}
I void GetAns(CI x,CI lst,CI v)//求解答案
{
ans[x]=p,(Sz[x]+v<<1)>=n&&--ans[x];//判断答案是否可以减1
for(RI i=lnk[x];i;i=e[i].nxt) e[i].to^lst&&(GetAns(e[i].to,x,v),0);//遍历子树
}
I bool cmp(CI x,CI y) {return Sz[x]>Sz[y];}//按Size排序
int main()
{
RI i,x,y;for(F.read(n),i=1;i^n;++i) F.read(x,y),add(x,y),add(y,x);//读入+建边
for(Mx[rt=0]=INF,GetRt(1),Init(rt),cnt=0,i=lnk[rt];i;i=e[i].nxt) s[++cnt]=e[i].to;//找到重心,将重心儿子存下来用于排序
for(sort(s+1,s+cnt+1,cmp),x=0,i=1;i<=cnt&&(x<<1)<n;++i) x+=Sz[s[i]];p=i-1;//排序,然后求出p
for(i=1;i<=cnt;++i) GetAns(s[i],rt,x-max(Sz[s[i]],Sz[s[p]]));//枚举子节点处理答案
for(i=1;i<=n;++i) F.writeln(ans[i]);return F.clear(),0;//输出
}
待到再迷茫时回头望,所有脚印会发出光芒