COI 2020 Pastiri(贪心)
COI 2020 Pastiri
题目大意
- 一棵大小为 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})(i\in[1,m]) disposx,posy=min(disx,posi)(i∈[1,m]),其中 p o s i pos_i posi表示 i i i所在位置。
题解
- 这题要用到一种贪心策略,每次找到剩余羊中深度最大的,然后在满足牧羊人能看守到它的情况下使牧羊人深度尽量小,再把当前牧羊人能看守到的羊都标记上,表示无需再为它们放置新的牧羊人。
- 感性考虑一下为什么是对的?
- 当前找到深度最大的羊,因为没有比它深度更大的了,所以牧羊人放在它子树内的话答案不会更优。同理,它到根的链上伸出的子树中也不存在比它深度大的点,那么放在这些子树内也不会更优。既然不会受到其它限制的话,为了能看守到更多的羊,则使牧羊人尽可能往上放。
- (默认结论正确后)那么可以先跑一遍最短路求出每个点到任意一只羊的最短距离, 再按深度从大到小枚举每只羊,不断跳父亲直到不合上述条件为止。找到牧羊人后,从他开始DFS一遍,把可行的羊标记上即可。
- 复杂度呢?
- 暴力往上跳父亲的过程,每条边至多被跳一次;
- 从牧羊人开始DFS的过程,最坏情况还是到 O ( N 2 ) O(N^2) O(N2),如何优化?其实有很多没必要DFS的地方,每次递归下去当且仅当该儿子子树内存在到牧羊人距离小于等于该次看守距离(指当前枚举到的羊和牧羊人之间的距离)的点,直接判断儿子节点的最短路 d i s dis dis即可 。在此条件下,一个点只会到达一次,再记录一下即可。
- 注意不能只记录一个点是否被经过,必须在满足前一个条件的基础上。
代码
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
#define N 500010
int 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;
void add(int x, int y) {
to[++len] = y;
nxt[len] = last[x];
last[x] = len;
}
void dfs(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);
}
void SPFA() {
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;
}
}
int cmp(int x, int y) {
return dp[x] > dp[y];
}
void solve(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);
}
int read() {
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;
}
int main() {
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\n", ans[0]);
for(i = 1; i <= ans[0]; i++) printf("%d ", ans[i]);
return 0;
}
哈哈哈哈哈哈哈哈哈哈