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;
}
posted @ 2021-01-10 21:14  OdtreePrince  阅读(123)  评论(0编辑  收藏  举报