题意
给定一棵树,要求所有节点被“消防站”覆盖,“消防站”的覆盖范围是距离本身距离不大于 2 2 2 的所有节点。求设立消防站的最少数量。
节点数 1 ≤ n ≤ 1000 1\le n\le 1000 1≤n≤1000
思路
树形 dp。感觉这题无从下手的原因就是不知道怎么设状态。
我们知道,节点要么本身是消防站,要么被消防站覆盖,考虑联系节点 u u u 本身与覆盖自己的消防站 k k k。
令 f u , k f_{u,k} fu,k 表示节点 u u u 被 k k k 覆盖,且 u u u 子树内所有点已经完成了覆盖操作。
鉴于节点数 n n n 比较小,因此 Θ ( n ) \Theta (n) Θ(n) 枚举覆盖自己的消防站 k k k,做到 Θ ( n 2 ) \Theta (n^2) Θ(n2) 的复杂度也是可以过的。
预处理树上各节点距离 d i s dis dis。当 d i s ( u , k ) ≤ 2 dis(u,k)\le 2 dis(u,k)≤2 时,那么 k k k 可以成为覆盖 u u u 的消防站,初始化 f ( u , k ) = 1 f(u,k)=1 f(u,k)=1,再去研究 u u u 的子树来更新 f ( u , k ) f(u,k) f(u,k)。
然后枚举 u u u 的子节点 v v v,如果 u u u 和 v v v 都被 k k k 覆盖,且 v v v 被 k k k 覆盖时最优(此时枚举的 dp 状态下, v v v 必然被 k k k 覆盖),那么可以取 f ( u , k ) ← f ( v , k ) − 1 f(u,k) \leftarrow f(v,k)-1 f(u,k)←f(v,k)−1,即把一开始初始化的 1 1 1, k k k 为 u u u 而建的消防站拆掉。
如果
v
v
v 被
k
k
k 覆盖时并非最优,那么怎么获取
v
v
v 的最优覆盖信息呢?不妨引入
n
u
m
u
num_u
numu 表示,在
u
u
u 子树内节点全部成功覆盖、所需最少的消防站数量。更新
n
u
m
u
num_u
numu 其实是简单的:
n
u
m
u
=
min
k
=
1
,
d
i
s
(
u
,
k
)
≤
2
n
f
(
u
,
k
)
num_u=\min_{k=1,\ dis(u,k)\le 2}^{n}f(u,k)
numu=k=1, dis(u,k)≤2minnf(u,k)
那么最终
f
(
u
,
k
)
f(u,k)
f(u,k) 的式子也可以写出来了:
f
(
u
,
k
)
=
∑
v
∈
s
o
n
u
min
(
n
u
m
u
,
f
(
v
,
k
)
−
1
)
f(u,k)=\sum_{v\in son_u}\min(num_u,f(v,k)-1)
f(u,k)=v∈sonu∑min(numu,f(v,k)−1)
为什么这样就是对的? n u m u num_u numu 不会记下了 v v v 被 k k k 覆盖时最优的情况的吗?因为 n u m u num_u numu 就算记下了 v v v 被 k k k 覆盖时最优的情况,也不会比 min \min min 的另一项 f ( v , k ) − 1 f(v,k)-1 f(v,k)−1 更优,因而不会算重。
最终答案就是 n u m 1 num_1 num1。
均摊下来 Θ ( n 2 ) \Theta (n^2) Θ(n2)。
代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll N=1003,inf=0x3f3f3f3f;
ll n;
ll idx,head[N];
struct edge
{
ll to,next;
}e[N<<1];
void addedge(ll u,ll v)
{
idx++;
e[idx].to=v;
e[idx].next=head[u];
head[u]=idx;
}
ll dis[N][N],f[N][N],num[N];
//dis(u,v):树上u,v距离
//f(u,k):u及其子树已经被覆盖、u被i覆盖的的最少消防站个数
//num(u):u子树内点集恰好覆盖完,最少消防站个数
void dfs(ll u,ll fa,ll rt)
{
for(int i=head[u];i;i=e[i].next)
{
ll v=e[i].to;
if(v==fa)continue;
dis[rt][v]=dis[v][rt]=dis[rt][u]+1;
dfs(v,u,rt);
}
}
void dp(ll u,ll fa)
{
for(int i=head[u];i;i=e[i].next)
{
ll v=e[i].to;
if(v==fa)continue;
dp(v,u);
}
for(int k=1;k<=n;k++)
{
if(dis[u][k]>2)continue;
f[u][k]=1;
for(int i=head[u];i;i=e[i].next)
{
ll v=e[i].to;
if(v==fa)continue;
f[u][k]+=min(num[v],f[v][k]-1);
//v已经被k覆盖过,-1
//若v最优消防站num(v)为k:f(v,k),并非最小不会计算
//即会计算最优消防站不为k的情况
}
num[u]=min(num[u],f[u][k]);
}
}
int main()
{
scanf("%lld",&n);
for(int i=2;i<=n;i++)
{
ll v;
scanf("%lld",&v);
addedge(i,v);
addedge(v,i);
}
for(int i=1;i<=n;i++)
dfs(i,0,i);
for(int i=1;i<=n;i++)
{
num[i]=inf;
for(int j=1;j<=n;j++)
f[i][j]=inf;
}
dp(1,0);
printf("%lld",num[1]);
return 0;
}