记一类树上点覆盖问题
jokersen
·
·
算法·理论
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 一样做。
例题
P2899
同 #2。
P2279
## [P3942](https://www.luogu.com.cn/problem/P3942)
$k\le20$ 时的 #3。
## [P3523](https://www.luogu.com.cn/problem/P3523)
二分答案后变为 #3。
::::info[代码]
```cpp
#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](https://www.luogu.com.cn/problem/P8428)
先处理出每个点距离最近的羊的距离。
按深度贪心,发现一个点如果往父亲跳还能覆盖到它,那么一定不劣,分析是类似的。
不过貌似这题要覆盖的点有良好的性质,直接暴力也是对的。
::::info[代码]
```cpp
#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;
}
```
::::
---
等看到新的题再加上去。