给出一棵n个节点的树,和一些树上的路径,求最少选出多少个点能使得每条路径里都至少有一个选出的点。
1<=n<=10^5
这题的经典版就是树是一条单链。
初一的贪心做法是按照右端点排序,然后每次选右端点最前的区间的右端点,把覆盖它的区间delete掉,继续做,证明显然。
除去排序的复杂度,可以O(n)扫一遍做到。
树上的话就比较6了。
随便选一个点为根,搞出dfs序,把各条路径的lca按dfs序排序(从大到小),接着像序列上那样搞。
证明大概是因为我按dfs序排序,假设我选了当前这条路径上的一个k点,它同时存在于之后的一条路径里,那么当前这条路径的lca也一定在之后那条路径里,所以我选lca是最优的。
用树链剖分维护一下即可。
Code:
#include
#include
#include
#define fo(i, x, y) for(int i = x; i <= y; i ++)
#define fd(i, x, y) for(int i = x; i >= y; i --)
using namespace std;
const int N = 500005;
int n, m, k, x, y;
int final[N], tot;
struct edge {
int to, next;
}e[N];
void link(int x, int y) {
e[++ tot].next = final[x], e[tot].to = y, final[x] = tot;
e[++ tot].next = final[y], e[tot].to = x, final[y] = tot;
}
struct AA {
int x, y;
}b[N];
void Init() {
scanf("%d %d", &n, &m);
fo(i, 1, m) {
scanf("%d %d", &x, &y);
link(x, y);
}
scanf("%d", &k);
fo(i, 1, k) scanf("%d %d", &b[i].x, &b[i].y);
}
int dfn[N], t_dfn, bz[N], fa[N], dep[N], top[N], w[N], tt, siz[N], son[N];
void dg(int x) {
bz[x] = 1;
siz[x] = 1;
dfn[x] = ++ t_dfn;
for(int i = final[x]; i; i = e[i].next) {
int y = e[i].to; if(bz[y]) continue;
dep[y] = dep[x] + 1;
fa[y] = x;
dg(y);
son[x] = siz[son[x]] > siz[y] ? son[x] : y;
siz[x] += siz[y];
}
bz[x] = 0;
}
void dg2(int x) {
bz[x] = 1;
if(top[x] == 0) top[x] = x;
w[x] = ++ tt;
if(son[x]) top[son[x]] = top[x], dg2(son[x]);
for(int i = final[x]; i; i = e[i].next) {
int y = e[i].to; if(bz[y] || y == son[x]) continue;
dg2(y);
}
bz[x] = 0;
}
int lca(int x, int y) {
while(top[x] != top[y])
if(dep[top[x]] > dep[top[y]])
x = fa[top[x]]; else y = fa[top[y]];
return dep[x] < dep[y] ? x : y;
}
int t[N * 10];
void add(int i, int x, int y, int l) {
if(x == y) {
t[i] = 1; return;
}
int m = (x + y) >> 1;
if(l <= m) add(i + i, x, m, l); else add(i + i + 1, m + 1, y, l);
t[i] = t[i + i] | t[i + i + 1];
}
int find(int i, int x, int y, int l, int r) {
if(x == l && y == r) return t[i];
int m = (x + y) / 2;
if(r <= m) return find(i + i, x, m, l, r); else
if(l > m) return find(i + i + 1, m + 1, y, l, r); else
return find(i + i, x, m, l, m) | find(i + i + 1, m + 1, y, m + 1, r);
}
int final2[N], tot2; edge e2[N];
void link2(int x, int y) {
e2[++ tot2].next = final2[x], e2[tot2].to = y, final2[x] = tot2;
}
void Built() {
fo(i, 1, k) {
int z = lca(b[i].x, b[i].y);
link2(z, i);
}
}
int ni[N], a[N];
int pd(int x, int y) {
while(top[x] != top[y]) {
if(dep[top[x]] < dep[top[y]]) swap(x, y);
if(find(1, 1, n, w[top[x]], w[x])) return 1;
x = fa[top[x]];
}
if(dep[x] > dep[y]) swap(x, y);
return find(1, 1, n, w[x], w[y]);
}
void End() {
fo(i, 1, n) ni[dfn[i]] = i;
fd(i, n, 1) {
int ans = 1;
x = ni[i];
for(int j = final2[x]; j; j = e2[j].next) {
y = e2[j].to;
if(!pd(b[y].x, b[y].y)) {
ans = 0; break;
}
}
if(!ans) {
add(1, 1, n, w[x]);
a[++ a[0]] = x;
}
}
printf("%d\n", a[0]);
fo(i, 1, a[0]) printf("%d ", a[i]);
}
int main() {
freopen("ping.in", "r", stdin);
freopen("ping.out", "w", stdout);
Init();
dep[1] = 1; dg(1);
dg2(1);
Built();
End();
}