1. 程式人生 > 實用技巧 >LOJ - 6042「雅禮集訓 2017 Day7」跳蚤王國的宰相

LOJ - 6042「雅禮集訓 2017 Day7」跳蚤王國的宰相

\(\text{Bi Bi Time!}\)

那是一天之前,親愛的教練為我們拉了一套提高組難度題目,無人上 \(100 \text{pts}\)

\(\text{Solution}\)

首先有個結論:被欽定節點的所有子樹大小不超過 $\lfloor \frac{n}{2}\rfloor $。

這個結論來源一個尋找最優解的思路:假設 \(v,u\) 之間有一條邊,我們可以把以 \(u\) 為根節點的樹除去 \(v\) 這棵子樹的一團子樹稱為左子樹(\(x\)),把以 \(v\) 為根節點的樹除去 \(u\) 這棵子樹的一團子樹稱為右子樹(\(y\))。

那麼假設我們求出 \(u\) 的距離和是 \(dis\)

\(v\) 的距離和就是 \(dis+(size_x+1)-(size_y+1)=dis+size_x-size_y\)。那麼如果現在有 \(size_x<size_y\)\(v\) 是會比 \(u\) 更優的,一直這樣走下去,發現當 \(size_x=size_y\) 時再走就會更劣。此時的 \(u,v\) 就是最優解(當然這是偶數的情況,奇數就只有一個是最優解)。

所以結論得證。

那問題就轉化成切多少刀可以使此點的每個子樹大小不超過 $\lfloor \frac{n}{2}\rfloor $。好吧到這一步我還是不會做

貪心地想,我們砍邊的花費都是一樣的,每次肯定儘量砍 \(size\)

最大的子樹(至於子樹的大小限制我們就直接懟給子樹自己來做就行了)。

定義 \(f[u]\) 為使 \(u\) 為根的子樹的大小不超過 $\lfloor \frac{n}{2}\rfloor $ 的最小砍次數,\(tot[u]\) 為砍完之後還剩多少個點。

注意這裡子樹 \(f[u_{son}]\) 的最優解一定會貢獻給 \(f[u]\),因為就算把整棵子樹 \(u_{son}\) 砍下來,如果不能維持其大小不超過 $\lfloor \frac{n}{2}\rfloor $ 也是白搞。

我們先只管子樹的情況做一遍 \(\text{DFS}\),關於上面父親那一坨的情況就是換根 \(\text{DP}\)

第一遍 \(\text{DFS}\) 能得到根節點的答案,後面的 \(\text{DP}\) 就是需要維護父親那一坨大小不超過 $\lfloor \frac{n}{2}\rfloor $,把父親的 \(tot[fa_{son}]\) 和父親的父親那一坨剩下的 \(tot\) 排個序,依次選取即可,不過因為對於父親的每個兒子都要排除自己的 \(tot\),所以像第一遍 \(\text{DFS}\) 的直接線性選取不行,做個字首和二分即可。

總時間複雜度 \(\mathcal O(n \log n)\)

\(\text{Code}\)

#include <cstdio>

#define rep(i,_l,_r) for(register signed i=(_l),_end=(_r);i<=_end;++i)
#define fep(i,_l,_r) for(register signed i=(_l),_end=(_r);i>=_end;--i)
#define erep(i,u) for(signed i=head[u],v=to[i];i;i=nxt[i],v=to[i])
#define efep(i,u) for(signed i=Head[u],v=to[i];i;i=nxt[i],v=to[i])
#define print(x,y) write(x),putchar(y)

template <class T> inline T read(const T sample) {
    T x=0; int f=1; char s;
    while((s=getchar())>'9'||s<'0') if(s=='-') f=-1;
    while(s>='0'&&s<='9') x=(x<<1)+(x<<3)+(s^48),s=getchar();
    return x*f;
}
template <class T> inline void write(const T x) {
    if(x<0) return (void) (putchar('-'),write(-x));
    if(x>9) write(x/10);
    putchar(x%10^48);
}
template <class T> inline T Max(const T x,const T y) {if(x>y) return x; return y;}
template <class T> inline T Min(const T x,const T y) {if(x<y) return x; return y;}
template <class T> inline T fab(const T x) {return x>0?x:-x;}
template <class T> inline T gcd(const T x,const T y) {return y?gcd(y,x%y):x;}
template <class T> inline T lcm(const T x,const T y) {return x/gcd(x,y)*y;}
template <class T> inline T Swap(T &x,T &y) {x^=y^=x^=y;}

#include <algorithm>
using namespace std;

const int maxn=1e6+5;

int n,head[maxn],nxt[maxn<<1],to[maxn<<1],cnt,f[maxn],tot[maxn],lim,ans[maxn],pre[maxn],fa_f[maxn],fa_tot[maxn];
struct node {
	int x,y;
} p[maxn];

void addEdge(int u,int v) {
	nxt[++cnt]=head[u],to[cnt]=v,head[u]=cnt;
	nxt[++cnt]=head[v],to[cnt]=u,head[v]=cnt;
}

bool cmp(node a,node b) {
	return a.x<b.x;
}

void dfs1(int u,int fa) {
	tot[u]=1;
	erep(i,u) {
		if(v==fa) continue;
		dfs1(v,u);
		f[u]+=f[v],tot[u]+=tot[v];
	}
	if(tot[u]<=lim) return;
	int Cnt=0;
	erep(i,u) if(v^fa) p[++Cnt].x=tot[v];
	sort(p+1,p+Cnt+1,cmp);
	while(tot[u]>lim) tot[u]-=p[Cnt--].x,++f[u];
}

void dfs2(int u,int fa) {
	ans[u]=fa_f[u]; int Cnt=0,l,r,mid,ToT,F,res;
	erep(i,u) if(v^fa) ans[u]+=f[v],p[++Cnt]=(node){tot[v],v};
	p[++Cnt]=(node){fa_tot[u],fa};
	sort(p+1,p+Cnt+1,cmp);
	rep(i,1,Cnt) pre[i]=pre[i-1]+p[i].x;
	rep(i,1,Cnt) {
		if(p[i].y==fa) continue;
		l=1,r=Cnt;
		while(l<=r) {
			mid=l+r>>1;
			ToT=pre[mid]+1,F=mid; // 列舉 mid 個可以剩下,其中 +1 是 u(因為對於 u 的兒子,把 (u,v) 砍了顯然沒有任何意義)
			if(i<=mid) ToT-=p[i].x,--F;
			if(ToT<=lim) res=mid,l=mid+1;
			else r=mid-1; 
		}
		ToT=pre[res]+1,F=res;
		if(i<=res) ToT-=p[i].x,--F;
		fa_tot[p[i].y]=ToT,fa_f[p[i].y]=ans[u]-f[p[i].y]+Cnt-F-1;
	}
	erep(i,u) if(v^fa) dfs2(v,u);
}

int main() {
	int u,v;
	n=read(9); lim=n>>1;
	rep(i,1,n-1) {
		u=read(9),v=read(9);
		addEdge(u,v);
	}
	dfs1(1,0); dfs2(1,0);
	rep(i,1,n) print(ans[i],'\n');
	return 0;
}