• [USACO18DEC]The Cow Gathering P


    首先可以思考一下每次能删去的点有什么性质。

    不难发现,每次能删去的点都是入度恰好为 (1) 的那些点(包括 (a_i ightarrow b_i) 的有向边)。 换句话说,每次能删去的点既要是树上的叶子节点,并且不会被任意一条有向边 (a_i ightarrow b_i) 指向。那么再来思考一下每个点能否走后离开。

    因为 (i) 号点只能最后离开,那么我们将 (i) 看作这课树的根(因为每次只能走叶子)。你会发现如果 (i) 不能最后走当前仅当会存在一条路径(走有向边) (x ightarrow y) 使得 (y)(x) 子树中的点,于是我们单次判断就能做到 (O(nm)) 了。那么这个判定条件有没有更为简单的描述呢?其实是存在的,你会发现如果我们将所有树边从儿子指向父亲,那么 (i) 不能最后走当且仅当这张有向图存在着一个环。于是这样单次判断的复杂度就能做到 (O(n + m)) 了。但这样的复杂度还不够,我们可能需要换一种方式思考。

    既然每次判断点不方便,我们能否考虑每条有向边对每个点的影响呢?事实上是可以的,不难发现对于任意一条有向边 (x ightarrow y),在以 (y) 为根时以 (x) 为根的子树内所有点为根时 (x ightarrow y) 就会在树上形成一个环,那么这些点都是不能最后删除的。那么我们怎么找到这些点呢?因为我们显然不可能每次都换根,可以先钦定 (1) 为树根。那么你会发现存在两种情况 (y)(x) 的祖先时,令 (f)(x ightarrow y) 这条链上 (y) 的儿子(可以 (O(log n)) 倍增求出,在 [USACO19JAN]Exercise Route P 中提到),那么这些点就会是整棵树除了以 (f) 为根的子树内的点。那么我们在根以及 (f) 上打标记差分即可。对于其他情况,这些点就会是以 (x) 为根的子树内的点,直接打标记即可。最终我们树上差分跑一边 (dfs) 即可。

    这样就做完了吗?事实上并没有,你会发现你忽略了有向边之间的影响。那么怎样的情况会对答案有影响呢?当且仅当形成了一条跨子树的路径 (x ightarrow cdots ightarrow y) 其中 (y)(x) 子树内的点,并且仔细分析你会发现,如果出现这种情况那么整张图是不存在这样的删除序列的。那么判掉是否对答案有贡献只需判断这张图是否有解即可,直接拓扑排序每次加入度数为 (1) 的点,最终如果存在没有入队的点就无解。

    #include <bits/stdc++.h>
    using namespace std;
    #define rep(i, l, r) for (int i = l; i <= r; ++i)
    #define dep(i, l, r) for (int i = r; i >= l; --i)
    #define Next(i, u) for (int i = h[u]; i; i = e[i].next)
    const int N = 100000 + 5;
    const int M = 20 + 5;
    struct edge {
    	int v, next;
    }e[N * 3];
    bool book[N];
    int n, m, u, v, tot, cnt, x[N], y[N], d[N], h[N], c[N], sz[N], dfn[N], dep[N], ans[N], f[N][M];
    queue <int> Q;
    int read() {
    	char c; int x = 0, f = 1;
    	c = getchar();
    	while (c > '9' || c < '0') { if(c == '-') f = -1; c = getchar();}
    	while (c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
    	return x * f;
    }
    void add(int u, int v) {
    	e[++tot].v = v, e[tot].next = h[u], h[u] = tot, ++d[v];
    }
    void dfs(int u, int fa){
    	f[u][0] = fa, sz[u] = 1, dfn[u] = ++cnt, dep[u] = dep[fa] + 1;
    	Next(i, u) {
    		int v = e[i].v; if(v == fa) continue;
    		dfs(v, u), sz[u] += sz[v];
    	}
    }
    int find(int x, int y) {
    	dep(i, 0, 20) if(dep[f[x][i]] > dep[y]) x = f[x][i];
    	return x;
    }
    void calc(int u, int fa, int tmp){
    	ans[u] = tmp + c[u];
    	Next(i, u) {
    		int v = e[i].v; if(v == fa) continue;
    		calc(v, u, tmp + c[u]);
    	}
    }
    int main() {
    	n = read(), m = read();
    	rep(i, 1, n - 1) u = read(), v = read(), add(u, v), add(v, u);
    	dfs(1, 0);
    	rep(j, 1, 20) rep(i, 1, n) f[i][j] = f[f[i][j - 1]][j - 1];
    	rep(i, 1, m) {
    		x[i] = u = read(), y[i] = v = read();
    		if(dfn[v] >= dfn[u] && dfn[v] <= dfn[u] + sz[u] - 1) ++c[1], --c[find(v, u)];
    		else ++c[u];
    	}
    	calc(1, 0, 0);
    	rep(i, 1, m) add(x[i], y[i]);
    	rep(i, 1, n) if(d[i] == 1) Q.push(i), book[i] = true;
    	while(!Q.empty()) {
    		int u = Q.front(); Q.pop();
    		Next(i, u) {
    			int v = e[i].v; if(book[v]) continue;
    			--d[v]; if(d[v] == 1) Q.push(v), book[v] = 1;
    		}
    	}
    	rep(i, 1, n) if(!book[i]) { 
    		rep(j, 1, n) puts("0");
    		return 0;
    	}
    	rep(i, 1, n) printf(ans[i] > 0 ? "0
    " : "1
    ");
    	return 0;
    }
    

    值得一提的是,判定性或定义型问题一定要去思考判定条件。另外,反向考虑每条边对答案的影响也是非常重要的。当发现自己的做法出现问题或考虑不全的时候,不要慌张,仔细分析看看能否以一种简单的方式解决这些问题。

    GO!
  • 相关阅读:
    golang交叉编译:Linux
    vmware共享文件夹
    虚拟机-Debian服务器配置
    day38--MySQL基础二
    day19-IO多路复用
    mysql 对时间的处理
    mysql 优化
    Linux性能查看
    day18-socket 编程
    JAVA 消耗 CPU过高排查方法
  • 原文地址:https://www.cnblogs.com/Go7338395/p/13708496.html
Copyright © 2020-2023  润新知