Codeforces 555E Case of Computer Network
https://codeforces.com/problemset/problem/555/E
题意:给一张 \(n\) 个点 \(m\) 条边的无向图,可能存在多重边,给 \(p\) 个 \((a,b)\) 对表示存在 \(a->b\) 的有向路径,询问是否存在一种构造方法(给每条边标方向)使得这 \(p\) 个条件同时成立,只用输出是否可能。
题解:
首先考虑在同一个边双连通分量中,必然可以构造出一种方案使得其中所有点两两之间可以互相到达。而剩余的割边会构造出一个森林结构。问题可简化成考虑在一棵树中,怎么给树边标方向使得各项不冲突,不同树中的两个点显然是不可达的。
那么在一颗树中,两点之间的路径一定有一部分是确定的,即 \(u->lca(u,v)->v\)。那么题目就变成了,如果维护每条边的方向。可以考虑维护每条亲子关系的树链的差分关系,具体来说,用 \(dp[i][0]\) 表示 \(i\) 号边(\(i\)节点表示的边是连向它父亲的那一条边,相当于边下放到点)向下的差分关系 \(dp[i][1]\) 表示 \(i\) 节点向上的差分关系,对于每一个条件 \((u,v)\),将 \(dp[lca(u,v)][0] + 1\),\(dp[u][0] - 1\),\(dp[lca(u,v)][1] + 1\),\(dp[v][1] - 1\),然后把亲子关系的值累加上去,就可以得到每个点的 \(dp[i][0],dp[i][1]\)。\(dp[i][0]\) 如果不为 \(0\) 表示这条边需要有向下的方向,\(dp[i][1]\) 不为 \(0\) 表示它需要有向上的方向,两者不能同时存在。
代码:
/*================================================================
*
* 创 建 者: badcw
* 创建日期: 2020/6/16 15:52
*
================================================================*/
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn = 2e5+50;
const int mod = 1e9+7;
ll qp(ll a, ll n, ll mod = ::mod) {
ll res = 1;
while (n > 0) {
if (n & 1) res = res * a % mod;
a = a * a % mod;
n >>= 1;
}
return res;
}
int n, m, q;
vector<int> edge[maxn];
int dfn[maxn], low[maxn], idx;
int st[maxn], stsz;
int dep[maxn], dp[maxn][20];
int inWhichTree[maxn], treeNow;
int inWhichGroup[maxn], groupNow, groupRt[maxn];
int fa[maxn], fatmp[maxn];
int f[maxn][2];
void dfs(int u, int fa) {
inWhichTree[u] = treeNow;
fatmp[u] = fa;
dfn[u] = low[u] = ++idx, st[++stsz] = u;
int firstToVisFa = 1;
for (auto v : edge[u]) {
if (v != fa || !firstToVisFa) {
if (!dfn[v]) {
dfs(v, u);
low[u] = min(low[u], low[v]);
} else low[u] = min(low[u], dfn[v]);
} else firstToVisFa = 0;
}
if (dfn[u] == low[u]) {
++groupNow;
groupRt[groupNow] = u;
do {
inWhichGroup[st[stsz]] = groupNow;
} while (st[stsz --] != u);
}
}
int lca(int u, int v) {
if (dep[u] > dep[v]) swap(u, v);
int dt = dep[v] - dep[u];
for (int i = 0; i < 20; ++i) if (dt >> i & 1) v = dp[v][i];
if (u == v) return u;
for (int i = 19; i >= 0; --i) {
if (dp[u][i] != dp[v][i]) u = dp[u][i], v = dp[v][i];
}
return dp[u][0];
}
int main(int argc, char* argv[]) {
scanf("%d%d%d", &n, &m, &q);
for (int i = 0; i < m; ++i) {
int u, v;
scanf("%d%d", &u, &v);
edge[u].push_back(v);
edge[v].push_back(u);
}
for (int i = 1; i <= n; ++i) {
if (!dfn[i]) {
treeNow ++;
dfs(i, 0);
}
}
for (int i = 1; i <= groupNow; ++i) dp[i][0] = fa[i] = inWhichGroup[fatmp[groupRt[i]]];
// dfs first then deal now, so if i is j's fa, i is greater than j.
for (int i = groupNow; i >= 1; --i) dep[i] = dep[fa[i]] + 1;
// 2e5 is less than 2^18
for (int i = 1; i < 20; ++i) {
for (int j = 1; j <= n; ++j) {
dp[j][i] = dp[dp[j][i - 1]][i - 1];
}
}
for (int i = 0; i < q; ++i) {
int u, v;
scanf("%d%d", &u, &v);
if (inWhichTree[u] != inWhichTree[v]) {
puts("No");
return 0;
}
u = inWhichGroup[u], v = inWhichGroup[v];
int tmp = lca(u, v);
f[tmp][0] ++, f[tmp][1] ++;
f[u][0] --, f[v][1] --;
}
// add subtree to the point
for (int i = 1; i <= groupNow; ++i) f[fa[i]][0] += f[i][0], f[fa[i]][1] += f[i][1];
for (int i = 1; i <= groupNow; ++i) {
if (f[i][0] && f[i][1]) {
puts("No");
return 0;
}
}
puts("Yes");
return 0;
}