【NOIP2017提高A组模拟10.5】Ping

题目大意:

给出一棵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();
}

你可能感兴趣的:(树分治,贪心)