51nod 1299 监狱逃离

原题链接.

初二的时候就听老曹讲过这题。

据说一个最小割就过了。

最小割特别显然。

每个点x->x’连代价为1的边,不要这个点就是割掉这条边。

对于每个有人的点x,S->x连正无穷。

对于每个叶子节点x,x->T连正无穷。

对于每条树边x->y,x->y’连正无穷。

跑最大流=最小割,就是答案。

100000需要梦想。

正解是个辣鸡树形dp。

选一个叶子节点为根。

fi,0/1/2 分别表示:
0.子树中人不能到,没有到叶子的路径。
1.子树中人不能到,有路径。
2.子树中人能到,没有路径。

强行dp,什么情况都丢进去就好了。

所以我的方程写的很丑。

Code:

#include 
#define fo(i, x, y) for(int i = x; i <= y; i ++)
#define min(a, b) ((a) < (b) ? (a) : (b))
using namespace std;

const int N = 1e5 + 5;

int n, m, x, y, r[N], bz[N];
int tot, next[N * 2], to[N * 2], final[N];

void link(int x, int y) {
    next[++ tot] = final[x], to[tot] = y, final[x] = tot;
    next[++ tot] = final[y], to[tot] = x, final[y] = tot;
}

int g, bx[N], f[N][3];

void dg(int x) {
    bx[x] = 1;
    if(r[x] == 1 && x != g) {
        f[x][2] = n;
        f[x][1] = 0;
        f[x][0] = 1;
        return;
    }
    if(bz[x]) f[x][2] = 0, f[x][0] = f[x][1] = n;
    int m0 = 1, s = 0;
    for(int i = final[x]; i; i = next[i]) {
        int y = to[i]; if(bx[y]) continue;
        dg(y);
        if(bz[x]) {
            f[x][2] += min(f[y][0], f[y][2]);
        } else {
            m0 += min(f[y][0], min(f[y][1], f[y][2]));
            f[x][0] += f[y][0];
            f[x][1] += min(f[y][0], f[y][1]);
            f[x][2] += min(f[y][0], f[y][2]);
        }
    }
    if(!bz[x]) f[x][0] = min(f[x][0], m0);
    if(f[x][0] > n) f[x][0] = n;
    if(f[x][1] > n) f[x][1] = n;
    if(f[x][2] > n) f[x][2] = n;
}

int main() {
    scanf("%d %d", &n, &m);
    n ++;
    fo(i, 1, n - 1) {
        scanf("%d %d", &x, &y); x ++; y ++;
        r[x] ++; r[y] ++;
        link(x, y);
    }
    fo(i, 1, m) {
        scanf("%d", &x); x ++;
        bz[x] = 1;
        if(r[x] == 1) {
            printf("-1"); return 0;
        }
    }
    g = 0;
    fo(i, 1, n) if(r[i] == 1)
        g = i;
    dg(g);
    f[g][2] ++;
    printf("%d", min(f[g][0], min(f[g][1], f[g][2])));
}

你可能感兴趣的:(网络流,树型dp,51nod)