摧毁图状树 [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 序对应位置修改为其深度,使用线段树来维护区间最小值即可。

两个有效的优化:

  • 我们记录一下从叶节点开始覆盖一次之后应该覆盖的那些节点,第一次直接从这些节点开始往上走。这是因为只要 \(k\) 稍微大一点,那么第一次覆盖中叶节点的个数甚至可能会比后面的所有节点的总数加起来还要多。

  • 当 \(k\) 超过了树高时,答案就是叶子结点的个数。直接输出即可。

#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;
}
上一篇:MySQL45讲之优化器选错索引


下一篇:基数排序