题意
给定一张无向图和若干个形如 ( x , y ) (x,y) (x,y) 的限制,表示 x x x 和 y y y 连通。
需要求出每条边是否能被确定方向,如果能确定方向则求出它的方向。
数据保证有解。
解法
首先观察到无向图中的边双连通分量中的边无法被定向。路径中经过一个边双的部分,一定可以像下图一样以另一个方向绕过这个边双。
- 蓝、绿色的路径是两条不同的从 x x x 到 y y y 的路径,显然在经过中间的“环”时每条边的走向是相反的,故每条边无法定向。
所以我们考虑把所有的边双缩成一个点,每一个边双中的边都标记为无法定向。这样原来的无向图就变成了一棵树,否则由于是无向图,一定还存在边双连通分量,可以进一步缩成点。建议画一张图理解。
- 假设原图缩完边双后是图(一),那么点 1 , 2 , 4 1,2,4 1,2,4 仍可以构成一个边双,将它们缩成一个点后的图(二)即是一棵树的结构。
我们就把图上问题转化为了树上问题。对于每个限制 ( x , y ) (x,y) (x,y),如果 x , y x,y x,y 不在同一边双内,由于树上两点的简单路径唯一,我们只需要将 x x x 和 y y y 对应的边双之间的路径进行标记即可。
实现
缩点过程可以使用 Tarjan
。计算每条边的方向时,树链剖分显然可行,但我们也可以采用树上差分。对于每个限制
(
x
,
y
)
(x,y)
(x,y)(
x
,
y
x,y
x,y 都是边双的编号且
x
≠
y
x\ne y
x=y),设差分数组为
d
d
d,则让
d
x
+
1
→
d
x
,
d
y
−
1
→
d
y
d_x+1\to d_x,d_y-1\to d_y
dx+1→dx,dy−1→dy,最后用 dfs
将每个结点的儿子的差分值累加至父亲的差分值里。对于每个点
x
x
x,如果
d
x
>
0
d_x>0
dx>0,则说明是该点指向它的父亲;如果
d
x
<
0
d_x<0
dx<0,则说明是它的父亲指向该点;否则就无法定向。可以手模几组样例看看这个差分法的原理。
时间复杂度:Tarjan
O
(
n
)
O(n)
O(n),dfs
统计答案
O
(
n
)
O(n)
O(n),总时间复杂度
O
(
n
)
O(n)
O(n)。
代码
#include<bits/stdc++.h>
using namespace std;
const int maxn = 4e5 + 5;
vector<int> mp[maxn];
vector<pair<int,int> > e[maxn];
void addEdge(int u,int v,bool op,int id = 0) {
if (op) {
mp[u].push_back(v);
return ;
}
e[u].push_back({v,id});
}
int dfn[maxn],low[maxn],st[maxn],b[maxn];
int top,tot,clo,U[maxn],V[maxn]; bool vis[maxn];
void tj(int u,int fa) {
dfn[u] = low[u] = ++ clo;
vis[st[++ top] = u] = true;
for (auto V : e[u]) {
int v = V.first;
if (V.second == fa) continue;
if (!dfn[v]) { tj(v,V.second); low[u] = min(low[u],low[v]); }
else if (vis[v]) low[u] = min(low[u],dfn[v]);
}
if (dfn[u] == low[u]) {
b[u] = ++ tot, vis[u] = false;
while (top > 0 && st[top] != u)
b[st[top]] = tot, vis[st[top --]] = false;
top --;
}
}
int m;
void build() {
int u,v;
for (int i = 1;i <= m;i ++) {
u = b[U[i]], v = b[V[i]];
if (u == v) continue;
addEdge(u,v,true);
addEdge(v,u,true);
}
}
int ans[maxn],dep[maxn];
void dfs(int u,int fa) {
dep[u] = dep[fa] + 1; // 顺便计算一下深度,方便后面统计答案
for (auto v : mp[u]) {
if (v == fa) continue;
dfs(v,u); ans[u] += ans[v];
}
}
int n,p;
int get(int x) { return x > 0 ? 0 : (x == 0 ? 1 : 2); }
int main() {
scanf("%d%d",&n,&m);
for (int i = 1,u,v;i <= m;i ++) {
scanf("%d%d",&u,&v);
U[i] = u, V[i] = v;
addEdge(u,v,false,i);
addEdge(v,u,false,i);
}
for (int i = 1;i <= n;i ++)
if (!dfn[i]) tj(i,0);
build(); scanf("%d",&p);
for (int i = 1,u,v;i <= p;i ++) {
scanf("%d%d",&u,&v);
ans[b[u]] ++; ans[b[v]] --;
}
for (int i = 1;i <= tot;i ++)
if (!dep[i]) dfs(i,0);
for (int i = 1;i <= m;i ++) {
int u = b[U[i]], v = b[V[i]];
if (u == v) putchar('B'); // 在同一个边双内,无法定向
else if (dep[u] > dep[v]) // u 的深度 > v,说明 v 是 u 的父亲
printf("%c","RBL"[get(ans[u])]);
else printf("%c","LBR"[get(ans[v])]); // 反之,u 是 v 的父亲
}
return 0;
}