BZOJ 3887/Luogu P3119: [Usaco2015 Jan]Grass Cownoisseur (强连通分量+最长路)

分层建图,反向边建在两层之间,两层内部分别建正向边,tarjan缩点后,拓扑排序求一次1所在强连通分量1+n所在强联通分量的最长路(长度定义为路径上的强联通分量内部点数和)。然后由于1所在强连通分量1+n所在强联通分量是相同的点,所以路径长度相当于有一头不计算,也就是一个半开半闭区间的形式。

最后还可能答案不用跑反向边,取一个较大值就行了

CODE

#include <bits/stdc++.h>
using namespace std;
const int MAXN = 200005;
const int MAXM = 300005;
int n, m, fir[MAXN], to[MAXM], nxt[MAXM], cnt, deg[MAXN], f[MAXN];
int dfn[MAXN], low[MAXN], tmr, q[MAXN], indx, scc[MAXN], tot, num[MAXN];
void tarjan(int u) {
    dfn[u] = low[u] = ++tmr;
    q[++indx] = u;
    for(int i = fir[u], v; i; i = nxt[i])
        if(!dfn[v=to[i]]) tarjan(v), low[u] = min(low[u], low[v]);
        else if(!scc[v]) low[u] = min(low[u], dfn[v]);
    if(dfn[u] == low[u]) {
        ++tot;
        do ++num[scc[q[indx]] = tot];
        while(q[indx--] != u);
    }
}
inline void link(int u, int v) {
    to[++cnt] = v; nxt[cnt] = fir[u]; fir[u] = cnt;
}
vector<int>G[MAXN];
int main () {
    scanf("%d%d", &n, &m);
    for(int i = 1, x, y; i <= m; ++i) {
        scanf("%d%d", &x, &y);
        link(x, y);
        link(y, x+n);
        link(x+n, y+n);
    }
    tarjan(1);
    for(int i = 1; i <= 2*n; ++i) if(dfn[i])
        for(int k = fir[i], j; k; k = nxt[k])
            if(scc[i] != scc[j=to[k]])
                G[scc[i]].push_back(scc[j]), ++deg[scc[j]];
    int l = 0, r = 0;
    for(int i = 1; i <= tot; ++i) {
        if(!deg[i]) q[r++] = i;
        f[i] = -0x3f3f3f3f;
    }
    while(l < r) {
        int u = q[l++]; if(u == scc[1]) f[u] = 0;
        for(int i = 0, v, siz = G[u].size(); i < siz; ++i) {
            if(!--deg[v=G[u][i]]) q[r++] = v;
            f[v] = max(f[v], f[u] + num[v]);
        }
    }
    printf("%d\n", max(f[scc[n+1]], num[scc[1]]));
}

这样的两分层图可以拓展到多层,网络流用的比较多吧。

posted @ 2019-12-14 14:50  _Ark  阅读(74)  评论(0编辑  收藏  举报