洛谷 P3387(tarjan缩点+求最长路)

强连通: 在一个有向图G里,设有两个点 a,b a , b ,由a有一条路可以走到b,由b又有一条路可以走到a,我们就叫这两个顶点(a,b)强连通。
强连通图: 如果 在一个有向图G中,每两个点都强连通,我们就叫这个图,强连通图。
强连通分量:在一个有向图G中,有一个子图,这个子图每2个点都满足强连通,我们就叫这个子图叫做 强连通分量 [分量::把一个向量分解成几个方向的向量的和,那些方向上的向量就叫做该向量(未分解前的向量)的分量]

原题地址:https://www.luogu.org/problemnew/show/P3387
思路:tarjan缩点之后,重新建图,使用spfa求解最长路。(就是最短路的变形)
具体见代码

/*
有向图求缩点
*/

#include 
using namespace std;
const int maxn = 1e4 + 5;
int n, m;
struct node {
    int nxt, v, u;
} e[maxn * 10];
int head[maxn], Index, dfn[maxn], low[maxn], scc, top;
int belog[maxn], tot, inStack[maxn], Stack[maxn], in[maxn];
//int num[maxn];//各个强连通分    量包含点的个数
int value[maxn], vis[maxn], dis[maxn];
vector<int>G[maxn];
int ret[maxn];
void init() {
    memset(vis, 0, sizeof(vis));//spfa中判断是否已经在队列中
    memset(dis, 0, sizeof(dis));//用于求距离
    memset(value, 0, sizeof(value));//保存点权
    memset(head, -1, sizeof(head));//用于前向星
    memset(belog, 0, sizeof(belog));//判断原来的顶点i属于缩点后的的哪个顶点
    memset(dfn, 0, sizeof(dfn));//时间戳
    memset(low, 0, sizeof(low));//判断顶点所能返回的最早的节点
    memset(in, 0, sizeof(in));//判断入度
    memset(inStack, 0, sizeof(inStack));//判断是否在栈中
//    memset(num, 0, sizeof(num));
    memset(ret, 0, sizeof(ret));//缩点后点权的总值
    Index = 0;//时间戳
    tot = 0;//前向星
    top = 0;//模拟栈
    scc = 0;//联通分量个数
}
void add_edge(int u, int v) {//加边操作
    e[tot].u = u;
    e[tot].v = v;
    e[tot].nxt = head[u];
    head[u] = tot++;
}
void tarjan(int u) {
    low[u] = dfn[u] = ++Index;//初始化
    inStack[u] = 1;//表明u节点已经在栈中
    Stack[top++] = u;//将u入栈
    for(int i = head[u]; ~i; i = e[i].nxt) {
        int v = e[i].v;
        if(!dfn[v]) {
            tarjan(v);
            low[u] = min(low[u], low[v]);
        } else if(inStack[v]) {
            low[u] = min(low[u], dfn[v]);
        }
    }
    if(low[u] == dfn[u]) {//如果想等,就说明找到了一个强连通分量
        scc++;
        int v;
        do {
            v = Stack[--top];
            inStack[v] = 0;
            belog[v] = scc;
//            num[scc]++;
            ret[scc] += value[v];
        } while(v != u);
    }
}
void solve() {
    for(int i = 1; i <= n; i++ ) {
            //为了求出所有的连通分量,如果保证是连通图,那么可以不用循环
        if(!dfn[i])tarjan(i);
    }
}
int spfa(int num) {
    queue<int>q;
    q.push(num);
    dis[num] = ret[num];
    while(!q.empty()) {
        int u = q.front();
        q.pop();
        vis[u] = 0;
        for(int i = 0; i < G[u].size(); i++ ) {
            int v = G[u][i];
            dis[v] = max(dis[v], dis[u] + ret[v]);
            if(!vis[v]) {
                q.push(v);
                vis[v] = 1;
            }
        }
    }
    int ans = 0;
    for(int i = 1; i <= scc; i++) ans = max(ans, dis[i]);
    return ans;
}
int main() {
    init();
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; i++) scanf("%d", &value[i]);
    for(int i = 1; i <= m; i++) {
        int u, v;
        scanf("%d%d", &u, &v);
        add_edge(u, v);
    }
    solve();
    for(int i = 0; i < tot; i++) {//重新构图
        int t1 = belog[e[i].u];
        int t2 = belog[e[i].v];
        if(t1 == t2) continue;
        G[t1].push_back(t2);
        in[t2]++;
    }
    int ans = 0;
    for(int i = 1; i <= scc; i++) {

        if(!in[i]) {//如果是最大值,那么是以入度为0的节点开始的
                //因为这一定是最大的 ,贪心
            memset(vis, 0, sizeof(vis));
            ans = max(ans, spfa(i));
        }
    }
    printf("%d\n", ans);
    return 0;
}
//5 6
//3 1
//1 2
//2 3
//4 3
//4 5
//5 3



你可能感兴趣的:(ACM_图论)