CF652E Pursuit For Aritifacts

谁说胖子不能爱 提交于 2020-04-04 23:05:13

题目传送门

 

这是一道很好的练习强联通的题目。 首先,从题中可以看到,题目的要求就是要我们求出从起点到终点是否可以经过flag = 1 的边。 由于是无向图,且要求很多,直接暴力dfs会很凌乱。

那么,我们就想到一个思路:能不能尽量把这张图缩小,标记转为点,最好成为一条一条链呢?

tarjan的缩点!!

没错,对于一个环,可以想到,只要这个环中有一条边flag = 1,那么所有的点我们都可以通过falg = 1的边到达(因为这是环)。所以,不妨进行tarjan缩点,只要这个缩点中有一条边falg = 1,我们就把这个缩点打上tag。

再一想,经过缩点之后,原来十分凌乱的图就变成了一棵树。到达终点的路线也就只有固定一条了。这里我选择dfs。

思路大体就是这样,总时间复杂度O(M + N)

话不多说,具体细节操作标记在代码里面了。

 

#include<bits/stdc++.h>
using namespace std;
#define N 500010
#define isdigit(c) ((c)>='0'&&(c)<='9')

inline int read(){
    int x = 0,s = 1;
    char c = getchar();
    while(!isdigit(c)){
        if(c == '-') s = -1;
        c = getchar();
    } 
    while(isdigit(c)){
        x = (x << 1) + (x << 3) + (c ^ '0');
        c = getchar();
    }
    return x * s;
}

int n, m;
struct node{
    int u, v, flag;
    int next;
} t[N << 1]; 
int f[N];//日常邻接表 

int s, ht;//起点, 终点 
int dfn[N], low[N], scc[N]; //scc即为缩点后每个缩点的编号 
stack <int> stac;//缩点用的
bool vis[N];//一个点是否被经过 

int bian = 0;
void add(int u, int v, int flag){
    bian++;
    t[bian].u = u;
    t[bian].v = v;
    t[bian].flag = flag;
    t[bian].next = f[u];
    f[u] = bian;
    return ;
}

int cnt = 0, cac;//cac为强联通的数量 
void tarjan(int now, int fa){//无向图的tarjan强联通 板子 
    dfn[now] = low[now] = ++cnt;
    vis[now] = 1;
    stac.push(now);
    for(int i = f[now]; i;i = t[i].next){
        int v = t[i].v;
        if(v != fa){
            if(!dfn[v]){
                tarjan(v, now);
                low[now] = min(low[now], low[v]);
            }
            else if(vis[v])low[now] = min(low[now], low[v]);
        }
    }
    if(dfn[now] == low[now]){
        int cur;
        cac++;
        do{
            cur = stac.top();
            stac.pop();
            scc[cur] = cac;
            vis[cur] = 0;
        }while(cur != now);
    }
    return ;
}

bool tong[N]; //tong为每个缩点被打上的标记,即上文所说的,是否包含flag = 1的边 
void dfs(int now, bool flag){
    if(tong[now])flag = 1;//这个缩点标记为1的话,记下来 
    if(now == scc[ht]){
        if(flag)puts("YES");//搜到终点,没什么好说的 
        else puts("NO");
        return ;
    }
    for(int i = f[now]; i;i = t[i].next){
        int v = t[i].v, u = t[i].u;
        if(!vis[v]){
            vis[v] = 1;
            dfs(v, flag | t[i].flag);//这里要注意不要漏掉了缩点与缩点之间的边的 flag 
        }   
    }
    return ;
}

int main(){
    n = read(), m = read();
    for(int i = 1;i <= m; i++){
        int x = read(), y = read(), tag = read();
        add(x, y, tag);add(y, x, tag);
    }
    s = read() , ht = read();
    tarjan(1, 0);
    for(int i = 1;i <= bian; i += 2){
        if(scc[t[i].u] == scc[t[i].v] && t[i].flag){
            tong[scc[t[i].u]] = 1;//为强联通分量中的边,且flag = 1 
        }
    }
    memset(f, 0, sizeof(f));//重复利用 
    bian = 0;
    memset(vis, 0, sizeof(vis));
    for(int i = 1;i <= m << 1; i++){
        int u = t[i].u, v = t[i].v;
        if(scc[u] != scc[v]){
            add(scc[u], scc[v], t[i].flag);//不同缩点之间的连边,需要保留。flag不能改 
        }
    }
    vis[scc[s]] = 1;
    dfs(scc[s], 0);//dfs的都是缩点,这点不要忘了 
    return 0;
}

 

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!