其他分享
首页 > 其他分享> > 摧毁图状树 [20220210 模拟赛] 贪心/调和级数/线段树

摧毁图状树 [20220210 模拟赛] 贪心/调和级数/线段树

作者:互联网

题意

给定一个 \(n\) 个节点的树,有 \(q\) 组询问,每次给出一个 \(k\),询问至少用几条长为 \(k\) 的祖先-后代链才能覆盖掉树上的所有节点。

\(n,q\le 10^5,k\le 10^9\)

题解

首先 \(k\) 这么大是唬人的;只要 \(k\) 超过了树高,答案就是叶子节点的个数。

考虑一个贪心策略:我们维护一个堆,每次从堆中选取深度最大的一个未被覆盖的叶节点 \(u\),从 \(u\) 往上走 \(k\) 个节点,并将这 \(k\) 个节点都打上标记;然后再往上走一个节点,将其加入堆中。

使用树剖配合线段树来维护节点的覆盖与查询,复杂度 \(O(\log ^2n)\)。

大多数情况下我们都覆盖了 \(k\) 个节点,因此操作一次的复杂度大概是 \(O\left(\dfrac{n}{k}\log ^2n\right)\)。

注意到 \(\sum\dfrac{n}{k}=O(n\ln n)\),因此总的复杂度大概是 \(O(n\log ^3n)\)。这是我场上的做法。

可以发现每次覆盖都是一条从后代到祖先的链,因此,一个节点被覆盖到了,当且仅当其子树内有一个和它距离不超过 \(k\) 的后代,在那里进行了覆盖。

子树的 DFS 序连续,因此我们可以每次在从一个节点向上覆盖时在其 DFS 序对应位置修改为其深度,使用线段树来维护区间最小值即可。

两个有效的优化:

#include<bits/stdc++.h>

using namespace std;

inline int read(){
	int x=0,f=1;char c=getchar();
	for(;(c<'0'||c>'9');c=getchar()){if(c=='-')f=-1;}
	for(;(c>='0'&&c<='9');c=getchar())x=x*10+(c&15);
	return x*f;
}

const int MN=1e5+5;

int n,Q;
int dep[MN],L[MN],R[MN];

vector<int>G[MN],S,nodes[MN];

#define lson(o) (o<<1)
#define rson(o) (o<<1|1)

const int INF=1e9;

struct SegTree{
	int d[MN<<2];

	inline void pushup(int o){
		d[o]=min(d[lson(o)],d[rson(o)]);
	}

	inline void build(){memset(d,63,sizeof(d));}

	inline int query(int l,int r,int ql,int qr,int o){
		if(l<=ql&&qr<=r)return d[o];
		int ans=INF,mid=(ql+qr)>>1;
		if(l<=mid)ans=min(ans,query(l,r,ql,mid,lson(o)));
		if(r>mid)ans=min(ans,query(l,r,mid+1,qr,rson(o)));
		return ans;
	}

	inline void modify(int pos,int k,int ql,int qr,int o){
		if(ql==qr){d[o]=k;return ;}
		int mid=(ql+qr)>>1;
		if(pos<=mid)modify(pos,k,ql,mid,lson(o));
		else modify(pos,k,mid+1,qr,rson(o));
		pushup(o);
	}
}tree;

int fa[MN][20],tot,dis[MN],maxdep;

void dfs(int u,int de){
	dep[u]=de,maxdep=max(maxdep,de);L[u]=++tot,dis[u]=INF;
	for(int i=1;i<=19;i++)fa[u][i]=fa[fa[u][i-1]][i-1];
	for(int v:G[u]){
		if(v==fa[u][0])continue;
		fa[v][0]=u;dfs(v,de+1);
		dis[u]=min(dis[v]+1,dis[u]);
	}
	if(G[u].size()==1&&(u!=1))dis[u]=0;
	nodes[dis[u]].push_back(u),R[u]=tot;
}

priority_queue<pair<int,int> >q;

#define mk make_pair
#define fi first
#define se second

int fak(int u,int k){
	for(int i=19;i>=0;i--)if(k>=(1<<i))k-=(1<<i),u=fa[u][i];
	return u;
}

int calc(int k){
	while(q.size())q.pop();S.clear();
	for(int u:nodes[k])q.push(mk(dep[u],u));
	while(q.size()){
		int u=q.top().se;q.pop();
		if(tree.query(L[u],R[u],1,n,1)-k<dep[u]||dis[u]<k)continue;
		tree.modify(L[u],dep[u],1,n,1),S.push_back(u);
		if(dep[u]>k){int v=fak(u,k);q.push(mk(dep[v],v));}
	}
	int res=S.size()+nodes[0].size();
	for(int u:S)tree.modify(L[u],INF,1,n,1);
	return res;
}

int ans[MN];

#define OK puts("OK")

signed main(void){

	freopen("tree.in","r",stdin);
	freopen("tree.out","w",stdout);

	n=read();
	for(int i=1;i<=n-1;i++){
		int u=read(),v=read();
		G[u].push_back(v),G[v].push_back(u);
	}

	dfs(1,1),tree.build();

	Q=read();
	while(Q--){
		int k=read();
		if(k>=maxdep)cout<<nodes[0].size()<<endl;
		else if(ans[k])cout<<ans[k]<<endl;
		else ans[k]=calc(k),cout<<ans[k]<<endl;
	}

	return 0;
}

标签:qr,覆盖,int,20220210,调和级数,图状树,ans,节点,define
来源: https://www.cnblogs.com/YunQianQwQ/p/xsy2667sol.html