1. 程式人生 > 實用技巧 >GJGHFD的關鍵點 題解 [倍增+線段樹+貪心]

GJGHFD的關鍵點 題解 [倍增+線段樹+貪心]

GJGHFD的關鍵點

Description:

​ 給定一棵大小為 \(n\) 的有根樹,結點編號從 \(1\) 開始,並且 \(1\) 號結點是根.
​ 你需要在這顆樹上選擇 \(k\) 個結點並將他們設為關鍵點. 一個點的祖先距離等於從它到根路徑上遇到的第一個關鍵點和它的距離. 當路徑上沒有關鍵點時祖先距離為 \(+∞\) . 一棵樹的祖先距離為樹上所有點祖先距離的最大值.
​ 對所有 \(k\) = \(1, 2, ··· , n\) ,求這棵樹祖先距離的最小值.

Input:

​ 輸入有多組資料.
​ 輸入的第一行一個整數 \(T\) ,表示資料組數.
​ 對於每組資料,第一行一個整數 \(n\)

,表示樹的大小.
​ 接下來一行 \(n − 1\) 個整數,第 \(i\) 個整數代表 \(i + 1\) 號結點的父親編號.
​ 保證輸入給出一棵樹.

Output:

​ 為了便於輸出,對於每組資料,請輸出 \(k = 1, 2, ··· , n\) 的所有答案的和.

Sample Input:

2 
3
1 2
3
1 1

Sample Output:

3 
2

Hint:

​ 對於\(100\%\)的資料,$1 \leq n \leq 2 \times 10^5, $$1 \leq \sum n \leq 1.2 \times10^6$ ,並且至多有 \(5\) 組資料滿足 \(n > 1000\)

.

​ 時間限制: \(5s\)

​ 空間限制: \(512M\)

題目分析:

​ 考慮對於每個 \(ans=i\) 有多少個 \(k\) 與之相對應,設其數量為 \(k_i\) ,則 \(ANS = \sum (i \times k_i)\).

​ 那我們再設對於每個 \(ans=i\) 所需要的 \(k\) 的最小值為 \(mink_i\),則容斥一下可得到\(k_i=mink_i-mink_{i+1}\).

​ 考慮如何計算 \(mink_i\) ,我們可以想到貪心,每次取出樹中尚未標記的深度最大的點,跳到該點的 \(i\) 級祖先,把這個祖先的子樹內所有點全部打上標記,表示這些點的祖先距離全部小於等於 \(i\)

。重複操作直到整棵樹都被標記。運算元就是所求的 \(mink_i\) ,打標記和求深度最大需要用到線段樹+dfs序,而跳到 \(i\) 級祖先需要倍增。

​ 程式碼如下(馬蜂很醜,不喜勿噴)——

#include<bits/stdc++.h>
#define Tp template<typename T>
#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 maxn 200005
#define LL long long
using namespace std;
int T,n,tot,maxdep,dep[maxn],f[maxn][20],maxx[maxn<<2],tag[maxn<<2],rk[maxn],dfn[maxn],low[maxn],fir[maxn],nxt[maxn],son[maxn];
inline void add(int x,int y){son[++tot]=y,nxt[tot]=fir[x],fir[x]=tot;}
inline void build(int now,int l,int r){
	if(!tag[now]) return;tag[now]=0;if(l==r){maxx[now]=rk[l];return;}int mid=l+r>>1;build(now<<1,l,mid),build(now<<1|1,mid+1,r);
	if(dep[maxx[now<<1]]>dep[maxx[now<<1|1]]) maxx[now]=maxx[now<<1];else maxx[now]=maxx[now<<1|1];
}
inline void modify(int now,int l,int r,int ll,int rr){
	if(!maxx[now]) return;tag[now]=1;if(l>=ll&&r<=rr){maxx[now]=0;return;}int mid=l+r>>1;
	if(mid>=ll) modify(now<<1,l,mid,ll,rr);if(mid<rr) modify(now<<1|1,mid+1,r,ll,rr);
	if(dep[maxx[now<<1]]>dep[maxx[now<<1|1]]) maxx[now]=maxx[now<<1];else maxx[now]=maxx[now<<1|1];
}
inline int jump(int x,int K){for(register int i=17;i>=0;i--) if((1<<i)&K) x=f[x][i];return x;}
inline void dfs(int x){
	maxdep=max(maxdep,dep[x]);for(register int i=1;i<=17;i++) f[x][i]=f[f[x][i-1]][i-1];dfn[x]=low[x]=++tot;rk[tot]=x;
	for(register int i=fir[x];i;i=nxt[i]) dep[son[i]]=dep[x]+1,f[son[i]][0]=x,dfs(son[i]),low[x]=low[son[i]];
}
class FileInputOutput
{
	private:
		static const int S=1<<21;
		#define tc() (A==B&&(B=(A=Fin)+fread(Fin,1,S,stdin),A==B)?EOF:*A++)
		#define pc(ch) (Ftop!=Fend?*Ftop++=ch:(fwrite(Fout,1,S,stdout),*(Ftop=Fout)++=ch))
		char Fin[S],Fout[S],*A,*B,*Ftop,*Fend; int pt[25];
	public:
		FileInputOutput(void) { Ftop=Fout; Fend=Fout+S; }
		Tp inline void read(T& x)
		{
			x=0; char ch; while (!isdigit(ch=tc()));
			while (x=(x<<3)+(x<<1)+(ch&15),isdigit(ch=tc()));
		}
		Tp inline void write(T x,const char& ch)
		{
			if (x<0) pc('-'),x=-x; RI ptop=0; while (pt[++ptop]=x%10,x/=10);
			while (ptop) pc(pt[ptop--]+48); pc(ch);
		}
		inline void flush(void)
		{
			fwrite(Fout,1,Ftop-Fout,stdout);
		}
		#undef tc
		#undef pc
}F;
int main(){
//	freopen("data.in","r",stdin);
	F.read(T);while(T--){
		F.read(n);for(register int i=1;i<=n;i++) fir[i]=0;tot=0;for(register int i=1;i<=n*4;i++) tag[i]=1;
		for(register int i=2,fa;i<=n;i++) F.read(fa),add(fa,i);maxdep=0,dep[1]=1,tot=0,dfs(1);LL ans=0;for(register int i=maxdep-1;i>=0;i--)
		{int now=0;build(1,1,n);int x=maxx[1];while(x){now++;x=jump(x,i);if(!x) x=1;modify(1,1,n,dfn[x],low[x]);x=maxx[1];}ans+=now-1;}F.write(ans,'\n');
	}
	return F.flush(),0;
}