P4652 [CEOI2017] One-Way Streets 题解

文章讲述了如何解决一个关于无向图中边定向的问题,通过将边双转换为树结构,利用Tarjan算法缩点,然后利用树上差分确定每条边的方向。文章详细介绍了缩点过程、定向策略和时间复杂度分析。
摘要由CSDN通过智能技术生成

题意

给定一张无向图和若干个形如 ( 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+1dx,dy1dy,最后用 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;
}
  • 21
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值