2020牛客暑期多校训练营(第四场)A Ancient Distance 二分 + 调和级数优化 +线段树维护

先考虑暴力如何处理:

已知标记点的个数最小化 最大AD距离不好求。

但反过来,已知最大AD距离x,求最少的标记点个数很好求:直接每次删除深度最大的点的x倍祖宗所在的子树直到全部删完。(每次至少删除x+1个节点,也就是说点数最多只需要n/(x+1)+1)

那么我们可以枚举最大AD距离,求出每个AD距离x下最少需要的标记点数y,那么  K >=y 时,最大AD距离一定小于等于y。

求出所有的然后dp滚一下即可。

下面考虑已知x如何 快速求出最少需要的标记点数:

由于每次删除,显然容易想到:用dfs序建立线段树维护每个dfs序对应节点的深度。

每次查找到深度最大的节点的x倍祖宗,然后删除其所在子树。(记录操作节点,方便后面初始化)

 

直到所有节点被删除,则找到了最少标记点数nm。

下面考虑时间复杂度:

每个距离x会找  n/x  次左右的点删除子树。

总共找: n+n/2+n/2+…………+n/n  =nln(n)次

每次需要log的复杂度在线段树上面,所以总体复杂度为:

n ln(n) * logn

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define re register
#define ls (o<<1)
#define rs (o<<1|1)
#define m (l+r)/2
#define pb push_back
const double PI= acos(-1.0);
const int M = 2e5+7;

int head[M],cnt=1;
void init(int n){cnt=1;for(int i=0;i<=n;i++)head[i]=0;}
struct EDGE{int to,nxt,w;}ee[M*2];
void add(int x,int y,int w){ee[++cnt].nxt=head[x],ee[cnt].w=w,ee[cnt].to=y,head[x]=cnt;}

int st[M],ed[M],sz,dep[M],dfn[M];
int f[M][21];
void dfs(int x,int fa){
//	cout<<" ============   "<<x<<"  "<<fa<<"  "<<sz+1<<endl;
	st[x]=++sz;
	dfn[sz]=x;
	f[x][0]=fa;
	for(int i=head[x];i;i=ee[i].nxt){
		int y=ee[i].to;
		if(y==fa)continue;
		dep[y]=dep[x]+1;
		dfs(y,x);
	}
	ed[x]=sz;
}
int gt(int x,int len){//x的len辈祖宗 
	for(int i=20;i>=0;i--){
		if(len>=(1<<i))x=f[x][i],len-=(1<<i);
	}
	return x;
}

vector<int>cg;//改变的节点 
int tr[M<<2],ps[M<<2],tg[M<<2],tr0[M<<2],ps0[M<<2];
void pu(int o){
	if(tr[ls]>=tr[rs])tr[o]=tr[ls],ps[o]=ps[ls]; 
	else tr[o]=tr[rs],ps[o]=ps[rs]; 
}
void pd(int o){
	if(tg[o]==0)return ;
	tr[ls]=tr[rs]=0;
	tg[ls]=tg[rs]=1;
	tg[o]=0;
}
void bd(int o,int l,int r){
	tg[o]=0;
	if(l==r){
		tr[o]=tr0[o]=dep[dfn[l]];
		ps[o]=ps0[o]=l;
		return ;
	}
	bd(ls,l,m);
	bd(rs,m+1,r);
	pu(o);
	tr0[o]=tr[o];
	ps0[o]=ps[o];
}
void up(int o,int l,int r,int x,int y){//区间赋值为0
	cg.pb(o);
 	if(x<=l&&r<=y){
 		tr[o]=0;tg[o]=1;
 		return ;
	}
	pd(o);
	if(x<=m)up(ls,l,m,x,y);
	if(y>m)up(rs,m+1,r,x,y);
	pu(o);
}

int ans[M];
int main()
{
  	int n;
	while(~scanf("%d",&n)){
		for(int i=0;i<=n;i++)head[i]=0,ans[i]=n+1;
		cnt=1;sz=0;
	  	int v;
	  	for(int i=2;i<=n;i++)scanf("%d",&v),add(i,v,1),add(v,i,1);
	  	dep[1]=1;dfs(1,0);
	  	for(int l=1;l<=20;l++)
	  		for(int i=1;i<=n;i++)
	  			f[i][l]=f[f[i][l-1]][l-1];
	  	bd(1,1,n);
	  //	for(int i=1;i<=n;i++)cout<<" == "<<i<<" "<<st[i]<<" "<<ed[i]<<endl;
	  	for(int x=1;x<n;x++)//枚举最大AD距离,求出最少需要多少个key,使得所有点的AD距离都小于等于x
	  	{
	  		int nm=0;
			cg.clear();
	  		while(1){//每个关键点会删去至少x+1个节点。最多执行n/x次,的单次均摊复杂度为log*调和级数 
	  			int mx=tr[1],id=dfn[ps[1]];
	  			if(mx==0)break;
	  			int fd=gt(id,x);
	  			nm++;
	  		//	cout<<fd<<" ****************- "<<mx<<" "<<id<<"   =  "<<endl;
	  			if(fd<=1)break;
	  			up(1,1,n,st[fd],ed[fd]);
			//	cout<<"                    "<<st[fd]<<"  "<<ed[fd]<<endl;	
			}
			//cout<<x<<" -------------------  "<<nm<<endl;
			for(auto x:cg)tr[x]=tr0[x],ps[x]=ps0[x],tg[x]=0;//初始化线段树 
			ans[nm]=min(ans[nm],x);
	    }
	    ll pr=0;
	    for(int i=2;i<n;i++) ans[i]=min(ans[i],ans[i-1]);
	    for(int i=1;i<n;i++)pr+=ans[i];
		//cout<<i<<" - "<<ans[i]<<endl;;
	    printf("%lld\n",pr);
	}
	return 0;
}
/*
7
1 1 2 2 3 3
9
1 1 2 2 3 3 4 7
10
1 2 3 2 1 3 5 3  2
*/

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值