[机房测试]信息拦截

Description

给定一个有向图,按顺序输出是 \(1\) 到 \(n\) 路径上的必经点且本身不在一个环内的所有点。

Solution

先缩点,建一个新图。对于包含在一个大于等于二的强连通分量内的点一定不会是答案,包含自环的点一定不是答案。从 \(1\) 和 \(n\) 分别 bfs 一遍,对能到达的点打上标记。那么必须同时包含两个标记才会成为答案。把不含两个标记的点删掉。又得到一个新图。会发现新图一个特点,就是先从 \(1\) 出发可以走不同路径,然后汇聚到一个点,再在出发又汇聚到一个点。实际上汇聚到的点就是答案。这些点可以通过拓扑排序求出,当入队时队列是空的,那么这就是一个可以汇聚到的点。
也可以发现这些点具有割点的性质,可以直接搞成无向图然后求割点。有点卡常。

#pragma GCC optimize(2)
#pragma GCC optimize("Ofast")
#pragma GCC optimize("inline")

#include<stdio.h>
#include<algorithm>
#include<vector>
#include<queue>
using namespace std;

inline int read(){
    int x=0,flag=1; char c=getchar();
    while(c<'0'||c>'9'){if(c=='-')flag=0;c=getchar();}
    while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+c-48;c=getchar();}
    return flag? x:-x;
}

const int N=5e5+7;

queue<int> Q;
vector<int> g[N],f[N],e[N];
int timer=0,scc=0,Tp=0,top=0;
int scc_sz[N],c[N],sta[N],ans[N],Id[N],dfn[N],low[N];
bool vis1[N],vis2[N],sef[N];

void tarjan(int u){
    dfn[u]=low[u]=++timer;
    sta[++top]=u;
    for(int v:f[u]){
        if(!dfn[v]){
            tarjan(v);
            low[u]=min(low[u],low[v]);
        }else if(!c[v])
            low[u]=min(low[u],dfn[v]);
    }
    if(low[u]==dfn[u]){
        int y; scc++;
        do{scc_sz[c[y=sta[top--]]=scc]++;}while(y!=u);
        if(scc_sz[scc]==1) Id[scc]=y;
        else Id[scc]=0;
    }
}

void Tarjan(int u){
    int flag=0;
    low[u]=dfn[u]=++timer;
    for(int v:e[u]){
        if(!dfn[v]){
            Tarjan(v);
            low[u]=min(low[u],low[v]);
            if(scc_sz[u]==1&&low[v]>=dfn[u]&&!sef[Id[u]]){
                flag++; if(flag>1||u!=c[1]) ans[++Tp]=Id[u];
            }
        }
        low[u]=min(low[u],dfn[v]);
    }
}

int main(){
    freopen("i.in","r",stdin);
    freopen("i.out","w",stdout);
    int T=read();
    while(T--){
        int n=read(),m=read();
        for(int i=1;i<=scc;i++)
            e[i].clear(),scc_sz[i]=0;
        timer=scc=0;
        for(int i=1;i<=n;i++){
            vis1[i]=vis2[i]=sef[i]=0;
            dfn[i]=low[i]=c[i]=0;
            g[i].clear(),f[i].clear();
        }
        for(int i=1,u,v;i<=m;i++){
            u=read(),v=read();
            if(u==v) sef[u]=1;
            else{
                f[u].push_back(v);
                g[v].push_back(u);
            }
        }
        tarjan(1);
        if(!dfn[n]){printf("0\n\n");continue;}
        Q.push(1),vis1[1]=1;
        while(!Q.empty()){
            int u=Q.front(); Q.pop();
            for(int v:f[u]){
                if(vis1[v]) continue;
                else Q.push(v),vis1[v]=1;
            }
        }
        Q.push(n),vis2[n]=1;
        while(!Q.empty()){
            int u=Q.front(); Q.pop();
            for(int v:g[u]){
                if(vis2[v]) continue;
                else Q.push(v),vis2[v]=1;
            }
        }
        for(int u=1;u<=n;u++){
            if(!vis1[u]||!vis2[u]) continue;
            for(int v:f[u]){
                if(!vis1[v]||!vis2[v]||c[u]==c[v]) continue;
                e[c[u]].push_back(c[v]);
                e[c[v]].push_back(c[u]);
            }
        }
        for(int i=1;i<=scc;i++) dfn[i]=low[i]=0;
        timer=0,Tp=0;
        if(scc_sz[c[n]]==1&&!sef[n]) ans[++Tp]=n;
        Tarjan(c[1]);
        if(scc_sz[c[1]]==1&&!sef[1]) ans[++Tp]=1;
        printf("%d\n",Tp);
        for(int i=Tp;i;i--) printf("%d ",ans[i]); putchar('\n');
    }
}
上一篇:leetcode344-反转字符串


下一篇:洛谷 P3513 [POI2011]KON-Conspiracy(2-sat方案数)