记一类树上点覆盖问题

1

有一棵树上有 \(n\) 个点,每个点都要被覆盖。

现在可以选一些点放置覆盖器,每个覆盖器可以与它 \(\le 0\) 的点,求最少放置数及方案。

这个显然是给每个点放一个覆盖器。

2

#1 中的每个覆盖器覆盖的距离换成 \(1\)

DP 输出方案很困难,考虑贪心。

随便选一个点作为根,记 \(f_x\) 表示点 \(x\) 的父亲。

不难想到按深度从大到小考虑,对于一个已经被覆盖的点直接略过。

现在如果点 \(x\) 没被覆盖,显然选 \(f_x\) 放覆盖器更优。因为 \(x\) 往下的节点已经全被覆盖,放 \(f_x\) 比放 \(x\) 还能多覆盖一些更上面的点。

对于每个 \(x\),判断是否被覆盖只要看 \(x,f_x\) 中有没有覆盖器即可,时间复杂度 \(\mathcal O(n)\)

3

#1 中的每个覆盖器覆盖的距离换成 \(k\)

可以像 #2 一样贪心放 \(k\) 级祖先,但这样判断是否被覆盖是 \(\mathcal O(k)\) 的,总时间复杂度为 \(\mathcal O(nk)\)

但可以发现一个性质,就是当 \(k\) 大的时候要放覆盖器的数量是不多的。感性理解一下就是当覆盖的范围很大,那么覆盖器就能覆盖很多的点,这样覆盖器的数量就不会多。

严谨一点的话可以考虑最坏情况,即一条链。此时要放覆盖器的数量是 \(\mathcal O(\dfrac{n}{k})\) 的。

所以考虑平衡修改(放覆盖器)与查询(判断是否被覆盖)的复杂度。

对于放覆盖器的点 \(x\),把要标记的点分成在它子树内和其它的两种。

对于第一种,将子树用 dfs 序变为区间。

发现如果只统计上面的覆盖器覆盖下面时,如果能覆盖到更下面的点时一定能覆盖到它。

那么我们设一个 dfs 序列它的意义为:

对于序列中的位置 \(i\),只统计上面的覆盖器覆盖下面时 \(i\) 对应的点 \(x\)\(\operatorname{dfn}(x)=i\)\(\operatorname{dfn}(x)\) 表示 \(x\)dfs 序中的编号)最大能被覆盖到多深,如果这个深度是 \(\ge x\) 的深度时 \(x\) 就是被覆盖的。

对于第二种,可以看作在 \(f_x\) 能覆盖子树中距离 \(\le k-1\) 的点,在 \(f_{f_x}\) 能覆盖子树中距离 \(\le k-2\) 的点,\(\dots\),是类似第一种的情况。

发现对于点 \(x\) 上面的覆盖器,我们用第一种统计了。对于 \(x\) 下面的覆盖器会被第二种统计,所以是正确的。

那么我们只需要做区间取 \(\max\),单点查询,可以用线段树维护。

这样的话处理第一种是 \(\mathcal O(\log n)\) 的,算上第二种一共 \(\mathcal O(k\log n)\),由于只会被修改 \(\mathcal O(\dfrac{n}{k})\) 次,所以总时间复杂度降为 \(\mathcal O(n\log n)\)

注意要判断是否存在 \(k\) 级祖先。

4

#3 但覆盖点不为全部点/覆盖距离会改变。

只要满足放 \(k\) 级祖先一定不劣就可以像 #3 一样做。

例题

P2279

\(k=2\) 时的 #3,当然 \(\mathcal O(nk)\) 也能过就是了。

代码略。

P3523

二分答案后就变为 #3 了。

#include<bits/stdc++.h>
#define N 300005
using namespace std;
vector<int>s[N];
int n,m,tot,a[N],p[N];
int fa[N],dep[N],siz[N],dfn[N];
class Segmentree{
	#define l(i) ((i)<<1)
	#define r(i) ((i)<<1|1)
	#define v(i) tr[i].val
	private:
		struct xs{
			int val;
		}tr[N<<2];
	public:
		void clear(){memset(tr,0,sizeof tr);}
		void upd(int ql,int qr,int v,int x=1,int l=1,int r=n){
			if(ql<=l&&qr>=r) return v(x)=max(v(x),v),void();
			int mid=l+r>>1;
			if(ql<=mid) upd(ql,qr,v,l(x),l,mid);
			if(qr>mid) upd(ql,qr,v,r(x),mid+1,r);
		}
		int query(int p,int x=1,int l=1,int r=n){
			if(l==r) return v(x);
			int mid=l+r>>1;
			v(l(x))=max(v(l(x)),v(x));
			v(r(x))=max(v(r(x)),v(x));
			if(p<=mid) return query(p,l(x),l,mid);
			return query(p,r(x),mid+1,r);
		}
	#undef l
	#undef r
	#undef v
}tr;
int dfs(int x){
	dep[x]=dep[fa[x]]+1;
	dfn[x]=++tot,siz[x]=1;
	for(auto p:s[x]) if(p^fa[x])
		fa[p]=x,siz[x]+=dfs(p);
	return siz[x];
}
bool check(int d){
	int cnt=0;
	tr.clear();
	for(auto x:p)
		if(a[x]&&tr.query(dfn[x])<dep[x]){
			if((++cnt)>m) return false;
			int t=x;for(int i=d;fa[t]&&i--;) t=fa[t];
			for(int i=d;t&&i>=0;t=fa[t],i--)
				tr.upd(dfn[t],dfn[t]+siz[t]-1,dep[t]+i);
		}
	return true;
}
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
		scanf("%d",a+i),p[i-1]=i;
	for(int i=1,u,v;i<n;i++)
		scanf("%d%d",&u,&v),
		s[u].emplace_back(v),
		s[v].emplace_back(u);
	dfs(1);int l=0,r=n-1;
	sort(p,p+n,[](int x,int y){return dep[x]>dep[y];});
	while(l<=r){
		int mid=l+r>>1;
		if(check(mid)) r=mid-1;
		else l=mid+1;
	}printf("%d",r+1);
	return 0;
}

P8428

先处理出每个点距离最近的羊的距离。

按深度贪心,发现一个点如果往父亲跳还能覆盖到它,那么一定不劣,分析是类似的。

不过貌似这题要覆盖的点有良好的性质,直接暴力也是对的。

#include<bits/stdc++.h>
#define N 500005
using namespace std;
bool a[N],b[N];
vector<int>s[N],ans;
int n,m,k,f[N],p[N];
int fa[N],dep[N],siz[N],dfn[N];
class Segmentree{
	#define l(i) ((i)<<1)
	#define r(i) ((i)<<1|1)
	#define v(i) tr[i].val
	private:
		struct xs{
			int val;
		}tr[N<<2];
	public:
		void upd(int ql,int qr,int v,int x=1,int l=1,int r=n){
			if(ql<=l&&qr>=r) return v(x)=max(v(x),v),void();
			int mid=l+r>>1;
			if(ql<=mid) upd(ql,qr,v,l(x),l,mid);
			if(qr>mid) upd(ql,qr,v,r(x),mid+1,r);
		}
		int query(int p,int x=1,int l=1,int r=n){
			if(l==r) return v(x);
			int mid=l+r>>1;
			v(l(x))=max(v(l(x)),v(x));
			v(r(x))=max(v(r(x)),v(x));
			if(p<=mid) return query(p,l(x),l,mid);
			return query(p,r(x),mid+1,r);
		}
	#undef l
	#undef r
	#undef v
}tr;
int dfs1(int x){
	dfn[x]=++m;
	f[x]=a[x]?-1:n;
	for(auto p:s[x]) if(p^fa[x])
		fa[p]=x,f[x]=min(f[x],dfs1(p));
	return ++f[x];
}
int dfs2(int x){
	siz[x]=1;
	dep[x]=dep[fa[x]]+1;
	f[x]=min(f[x],f[fa[x]]+1);
	for(auto p:s[x])
		if(p^fa[x]) siz[x]+=dfs2(p);
	return siz[x];
}
int main(){
    scanf("%d%d",&n,&k);
	for(int i=1,u,v;i<n;i++)
		scanf("%d%d",&u,&v),
		s[u].emplace_back(v),
		s[v].emplace_back(u);
	for(int i=1,x;i<=k;i++)
		scanf("%d",&x),a[x]=true;
	for(int i=1;i<=n;i++) p[i]=i;
	f[0]=n,dfs1(1),dfs2(1);
	sort(p+1,p+n+1,[](int x,int y){return dep[x]>dep[y];});
	for(int i=1;i<=n;i++)
		if(a[p[i]]&&tr.query(dfn[p[i]])<dep[p[i]]){
			int t=0;
			for(int x=p[i],j=0;f[x]==j;x=fa[x],j++) t=x;
			ans.emplace_back(t);
			for(int x=t,j=f[t];x&&j>=0;x=fa[x],j--)
				tr.upd(dfn[x],dfn[x]+siz[x]-1,dep[x]+j);
		}
	printf("%llu\n",ans.size());
	for(auto p:ans) printf("%d ",p);
    return 0;
}

等看到新的题再加上去。

posted @ 2026-01-22 11:43  Jokersen  阅读(5)  评论(0)    收藏  举报