ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

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

2022-02-11 08:32:20  阅读:189  来源: 互联网

标签:qr 覆盖 int 20220210 调和级数 图状树 ans 节点 define


题意

给定一个 \(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;
}

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

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有