P4652 [CEOI 2017] One-Way Streets

题目传送门

tarjan 求桥,缩点,数上差分,LCA,DSU。

题目大意

给定一张 \(n\) 个点 \(m\) 条边的无向图,可能有重边,现在想要把这张图定向。

\(p\) 个限制条件,每个条件形如 \((x_i,y_i)\) ,表示在新的有向图当中, \(x_i\) 要能够沿着一些边走到 \(y_i\)

现在请你求出,每条边的方向是否能够唯一确定。同时请给出这些能够唯一确定的边的方向。

\((1\leq n,m,p\leq 10^5)\)

题解

转化题意。

先不考虑无解的情况,注意无解应该要归结到无法确定方向这一类上。

一条边能确定方向,当且仅当某个限制条件中的点对必须经过该边,那么这条边的方向就确定了。

必须经过这个条件可以转化为两个约束:

  1. 该边在限制点对的路径上。
  2. 该边是桥,即割掉该边后图会被断开。

这个条件在对在环上的边显然永远不成立,也就是说在环上,也就是边双连通分量上的边一定是无法确定方向的。

换句话说,只有可能成为答案

那么我们可以先进行缩点(这样最直观,但不代表一定要缩点),这样整张图就变成了一个森林(注意原本的图不一定连通),这样每一条边都是原图的桥了,并且每一条限制只会产生一条路径,于是题意转化为:

一片森林被多条路径覆盖,求所有被覆盖的点

我们发现不好考虑每一条边是否被覆盖,显然好考虑的是每一条路径对答案的贡献,但是暴力覆盖一定是超时的。

我们显然可以进行优化,将一段路径拆成两端 \(a-lca(a,b)\)\(lca(a,b)-b\) ,对于单独的一条笔直的路径,显然是可以差分的,我们只需要在开头和结尾差分,最后对每棵树做一次前缀和即可,容易实现。

注意这里的路径是有方向的,向上和向下,要分两个数组分别维护。

现在考虑什么时候是无解的情况,显然当一个边被两个方向都覆盖了,那么它就无解了,这里枚举每条边看一下就好了。

完结撒花。

代码会很长很长,但是我这个思路应该是最无脑的了把。

代码

#include <bits/stdc++.h>

#define eb push_back
using namespace std;
const int N=1e5+5;
using PII = pair<int,int>;

namespace dsu{
    int fa[N];
    void init(int n){
        for(int i=1;i<=n;i++) fa[i]=i;
    }    
    int find(int x){
        if(x==fa[x]) return x;
        return fa[x]=find(fa[x]);
    }
    void merge(int x,int y){
        int fx=find(x),fy=find(y);
        if(fx==fy) return ;
        fa[fy]=fx; 
    }
    bool same(int x,int y){
        return find(x)==find(y);
    }
};

int n,m,p;
int xx[N],yy[N],ago[N],ans[N];
struct EdgeNode {
    int to, id;
};
vector<EdgeNode> e[N]; 
vector<int> E[N],rt,G[N];

struct Edge{
    int x,y;
    bool z;
    int id;
    bool operator < (const Edge &b){
        if(x!=b.x) return x<b.x;
        return y<b.y;
    }
};
vector<Edge> edge;

pair<bool,int> find(int u,int v){
    int l=0,r=edge.size()-1,res=-1;
    while(l<=r){
        int mid=l+r>>1;
        if(edge[mid].x==u){
            if(edge[mid].y==v){
                res=mid;
                break;
            } 
            if(edge[mid].y<v) l=mid+1;
            else r=mid-1;
        }
        else{
            if(edge[mid].x<u) l=mid+1;
            else r=mid-1;  
        }
    }
    if(res==-1){
        cout<<"ERR\n";
        return make_pair(false,1);
    }
    
    return make_pair(edge[res].z,edge[res].id);
}

int dfn[N],low[N],Time;
int bel[N],ebcc_cnt;
vector<PII> br;
vector<bool> is_bridge;

void tarjan(int u, int in_edge) {
    dfn[u] = low[u] = ++Time;
    for (auto [v, id] : e[u]) {
        if (id == in_edge) continue;
        if (!dfn[v]) {
            tarjan(v, id);
            low[u] = min(low[u], low[v]);
            if (low[v] > dfn[u]) {
                br.eb({u, v});
                is_bridge[id] = true;
            }
        } else {
            low[u] = min(low[u], dfn[v]);
        }
    }
}

void dfs_ebcc(int u, int cnt) {
    bel[u] = cnt;
    for (auto [v, id] : e[u]) {
        if (!bel[v] && !is_bridge[id]) 
            dfs_ebcc(v, cnt);
    }
}

void build(){

    Time = 0;
    is_bridge.resize(m+1, false);
    

    for(int i=1;i<=n;i++)
        if(!dfn[i]) tarjan(i, 0);
    

    ebcc_cnt = 0;
    for(int i=1;i<=n;i++)
        if(!bel[i]) dfs_ebcc(i, ++ebcc_cnt);
    

    for(auto [u,v] : br){
        int bu = bel[u], bv = bel[v];
        E[bu].eb(bv);
        E[bv].eb(bu);
        dsu::merge(bu, bv);
    }
}

bool vis1[N];
int dep[N],fa[N];
void dfs1(int u){
    vis1[u]=true;
    for(int v:E[u]){
        if(vis1[v]) continue;
        dep[v]=dep[u]+1;
        fa[v]=u;
        dfs1(v);
    }
}

int f[N][20];
void pre(){
    for(int i=1;i<=ebcc_cnt;i++) f[i][0]=fa[i];
    for(int j=1;j<=17;j++)
        for(int i=1;i<=ebcc_cnt;i++)
            f[i][j]=f[f[i][j-1]][j-1];
}

int lca(int x,int y){
    if(dep[x]<dep[y]) swap(x,y);
    for(int i=17;i>=0;i--)
        if(dep[f[x][i]]>=dep[y]) x=f[x][i];
    if(x==y) return x;
    for(int i=17;i>=0;i--)
        if(f[x][i]!=f[y][i]){
            x=f[x][i];
            y=f[y][i];
        }
    return fa[x];
}

int col1[N],col2[N];
void draw(int u){
    for(int v:E[u]){
        if(v==fa[u]) continue;
        draw(v);
        col1[u]+=col1[v];
        col2[u]+=col2[v];
    }
}

int main(){
    freopen("oneway.in","r",stdin);
    freopen("oneway.out","w",stdout);
    
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    
    cin>>n>>m;
    
    for(int i=1;i<=m;i++){
        int u,v;
        cin>>u>>v;
        e[u].push_back({v, i});  
        e[v].push_back({u, i});
        edge.push_back({u, v, true, i});
        edge.push_back({v, u, false, i});
    }
    sort(edge.begin(),edge.end());

    cin>>p;
    for(int i=1;i<=p;i++)
        cin>>xx[i]>>yy[i];
        
    dsu::init(n);
    build(); 
    

    for(int i=1;i<=ebcc_cnt;i++)
        if(!vis1[i]){
            rt.eb(i);
            dfs1(i);
        }
    
    pre();

    for(int i=1;i<=p;i++){
        int X=bel[xx[i]], Y=bel[yy[i]];
        if(X==Y) continue;
        if(!dsu::same(X,Y)) continue;
        col1[X]++;
        col2[Y]++;
        int L=lca(X,Y);
        col1[L]--;
        col2[L]--;
    }
    
    for(int it:rt) draw(it);
    
    for(auto [u,v]:br){
        int U=bel[u], V=bel[v];
        if(dep[U]>=dep[V]) swap(U,V),swap(u,v);
        int tmp=0;
        if(col1[V]>0 && col2[V]>0) tmp=0;
        else if(col1[V]>0) tmp=1;
        else if(col2[V]>0) tmp=-1;
        
        auto [f,Id]=find(u,v);
        
        if(f) ans[Id] = -tmp;
        else ans[Id] = tmp;
    }
    
    for(int i=1;i<=m;i++) 
        if(ans[i]==1) cout<<'R';
        else if(ans[i]==-1) cout<<'L';
        else cout<<'B';
    return 0;     
}
posted @ 2025-09-18 11:12  _AzureSky  阅读(12)  评论(0)    收藏  举报