一棵大小为
N
N
N的树上有若干只羊,求能看守所有的
M
M
M只羊的最少牧羊人数量及任意一种方案。牧羊人
x
x
x能看守羊
y
y
y,当且仅当
d
i
s
p
o
s
x
,
p
o
s
y
=
m
i
n
(
d
i
s
x
,
p
o
s
i
)
(
i
∈
[
1
,
m
]
)
dis_{pos_x,pos_y}=min(dis_{x,pos_i})(iin[1,m])
disposx,posy=min(disx,posi)(i∈[1,m]),其中
p
o
s
i
pos_i
posi表示
i
i
i所在位置。
从牧羊人开始DFS的过程,最坏情况还是到
O
(
N
2
)
O(N^2)
O(N2),如何优化?其实有很多没必要DFS的地方,每次递归下去当且仅当该儿子子树内存在到牧羊人距离小于等于该次看守距离(指当前枚举到的羊和牧羊人之间的距离)的点,直接判断儿子节点的最短路
d
i
s
dis
dis即可 。在此条件下,一个点只会到达一次,再记录一下即可。
注意不能只记录一个点是否被经过,必须在满足前一个条件的基础上。
代码
#include<cstdio>#include<cstring>#include<algorithm>#include<queue>usingnamespace std;#define N 500010int last[N], nxt[N *2], to[N *2], len =1;int dis[N], dp[N], Fa[N], vi[N];int a[N], p[N], ans[N];
queue<int> q;voidadd(int x,int y){
to[++len]= y;
nxt[len]= last[x];
last[x]= len;}voiddfs(int k,int fa){
dp[k]= dp[fa]+1, Fa[k]= fa;for(int i = last[k]; i; i = nxt[i])if(to[i]!= fa)dfs(to[i], k);}voidSPFA(){while(!q.empty()){int x = q.front();
q.pop();for(int i = last[x]; i; i = nxt[i]){int y = to[i];if(dis[x]+1< dis[y]){
dis[y]= dis[x]+1;if(!vi[y]) q.push(y), vi[y]=1;}}
vi[x]=0;}}intcmp(int x,int y){return dp[x]> dp[y];}voidsolve(int k,int fa,int s){
p[k]=0, vi[k]=1;if(!s)return;for(int i = last[k]; i; i = nxt[i])if(dis[to[i]]== s -1&&!vi[to[i]])solve(to[i], k, s -1);}intread(){int s =0;char x =getchar();while(x <'0'|| x >'9') x =getchar();while(x >='0'&& x <='9') s = s *10+ x -48, x =getchar();return s;}intmain(){int n, m, i, x, y;scanf("%d%d",&n,&m);for(i =1; i < n; i++){
x =read(), y =read();add(x, y),add(y, x);}dfs(1,0);memset(dis,127,sizeof(dis));for(i =1; i <= m; i++){scanf("%d",&a[i]);
dis[a[i]]=0, vi[a[i]]=1;
q.push(a[i]);
p[a[i]]=1;}SPFA();sort(a +1, a + n +1, cmp);for(i =1; i <= m; i++)if(p[a[i]]){int x = a[i], t = x;while(Fa[t]&& dis[Fa[t]]== dis[t]+1) t = Fa[t];
ans[++ans[0]]= t;solve(t,0, dp[x]- dp[t]);}printf("%d
", ans[0]);for(i =1; i <= ans[0]; i++)printf("%d ", ans[i]);return0;}