Tarjan缩点题单 刷题题解

Tarjan缩点可以将一个图的每个强连通分量缩成一个点,然后构建新图,该图就会变成一个有向无环图。变成有向无环图之后就能结合最短路,拓扑......解决相应题目

洛谷题单分享:
https://www.luogu.com.cn/training/526565

前几道是绿题,没什么好写的,大致过一下

1.强连通分量

题目链接:https://www.luogu.com.cn/problem/B3609

题意:求出每个强连通分量,太模板了

AC代码:

#include<bits/stdc++.h>
#define int long long
#define endl '\n'
using namespace std;
const int N=1e4+10;
vector<int> e[N];
int dfn[N],low[N],tot,a[N];
int stk[N],instk[N],top;
int scc[N],siz[N],cnt;
void tarjan(int x) {
    dfn[x] = low[x] = ++tot;
    stk[++top] = x, instk[x] = 1;
    for (int y: e[x]) {
        if (!dfn[y]) {
            tarjan(y);
            low[x] = min(low[x], low[y]);
        } else if (instk[y])
            low[x] = min(low[x], dfn[y]);
    }
    if (dfn[x] == low[x]) {
        int y;
        ++cnt;
        do {
            y = stk[top--];
            instk[y] = 0;
            scc[y] = cnt;
            siz[cnt] += a[y];
        } while (y != x);
    }
}
int st[N];
void solve() {
    int n, m;
    cin >> n >> m;
    for (int i = 1; i <= m; i++) {
        int u, v;
        cin >> u >> v;
        e[u].push_back(v);
    }
    for (int i = 1; i <= n; i++) if (!dfn[i]) tarjan(i);
    cout << cnt << endl;
    vector<int> ans[N];
    for (int i = 1; i <= n; i++) {//这样能直接满足每个强连通分量中的节点编号从小到大
        ans[scc[i]].push_back(i);
    }
    for (int i = 1; i <= n; i++) {
        if (!st[i]) {
            for (auto k: ans[scc[i]]) {
                st[k] = 1;
                cout << k << " ";
            }
            cout << endl;
        }
    }
}
signed main() {
    ios_base::sync_with_stdio(0), cin.tie(0), cout.tie(0);
//    int t;
//    cin >> t;
//    while (t--)
    solve();
    return 0;
}

2.The Cow Prom S

题目链接:https://www.luogu.com.cn/problem/P2863

模板题,直接放代码了

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e5+10;
int n,m,a,b;
vector<int> e[N];
int dfn[N],low[N],tot;
int stk[N],instk[N],top;
int scc[N],siz[N],cnt;

void tarjan(int x) {
    dfn[x] = low[x] = ++tot;
    stk[++top] = x, instk[x] = 1;
    for (int y: e[x]) {
        if (!dfn[y]) {
            tarjan(y);
            low[x] = min(low[x], low[y]);
        } else if (instk[y])
            low[x] = min(low[x], dfn[y]);
    }
    if (dfn[x] == low[x]) {
        int y;
        ++cnt;
        do {
            y = stk[top--];
            instk[y] = 0;
            scc[y] = cnt;
            ++siz[cnt];
        } while (y != x);
    }
}
signed main() {
    cin >> n >> m;
    while (m--)
        cin >> a >> b, e[a].push_back(b);
    for (int i = 1; i <= n; i++)
        if (!dfn[i]) tarjan(i);
    int ans = 0;
    for (int i = 1; i <= cnt; i++)
        if (siz[i] > 1) ans++;
    cout << ans << endl;
    return 0;
}

3.受欢迎的牛

题目链接:https://www.luogu.com.cn/problem/P2341

如果有多个出度为0的强连通分量,那么受欢迎的牛就为0,否则,受欢迎的牛的数量就是该强连通分量的牛的数量

AC代码:

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e5+10;
int n,m;
vector<int> e[N];
int dfn[N],low[N],tot;//dfn[x]为节点第一次被访问的顺序(时间戳),low[x]为从节点x出发能访问的最早时间戳
int stk[N],instk[N],top;//标记是否在栈中
int scc[N],siz[N],cnt;//scc[x]计入x在哪个强连通分量中
int din[N],dout[N];
void tarjan(int x) {
    //入x时,盖戳、入栈
    dfn[x] = low[x] = ++tot;
    stk[++top] = x, instk[x] = 1;
    for (int y: e[x]) {
        if (!dfn[y]) {//若y尚未访问
            tarjan(y);
            low[x] = min(low[x], low[y]);//回x时更新low
        } else if (instk[y])//若y已访问且在栈中
            low[x] = min(low[x], dfn[y]);//在x时更新low
    }
    //离x时,收集SCC
    if (dfn[x] == low[x]) {//若x是SCC的根
        int y;
        ++cnt;
        do {
            y = stk[top--];
            instk[y] = 0;
            scc[y] = cnt;//SCC编号
            ++siz[cnt];//SCC大小
        } while (y != x);
    }
}
signed main() {
    cin >> n>>m;
    for (int i = 1; i <= m; i++) {
        int u,v;
        cin>>u>>v;
        e[u].push_back(v);
    }
    for (int i = 1; i <= n; i++)//可能不连通
        if (!dfn[i]) tarjan(i);
    for (int i = 1; i <= n; i++) {//scc缩点,将连通分量缩成一个点
        for (auto k: e[i]) {
            if (scc[i] != scc[k]) {
                dout[scc[i]]++;
                din[scc[k]]++;
            }
        }
    }
    int ans,num=0;
    for (int i = 1; i <= cnt; i++) {
        if (!dout[i]) ans+=siz[i],num++;
    }
    if(num==1) cout<<ans<<endl;
    else cout<<0<<endl;
    return 0;
}

4.【模板】缩点

题目链接:https://www.luogu.com.cn/problem/P3387

拓扑+缩点

AC代码:

#include<bits/stdc++.h>
#define int long long
#define endl '\n'
using namespace std;
const int N=1e4+10;
vector<int> e[N];
int dfn[N],low[N],tot,a[N];//dfn[x]为节点第一次被访问的顺序(时间戳),low[x]为从节点x出发能访问的最早时间戳
int stk[N],instk[N],top;//标记是否在栈中
int scc[N],siz[N],cnt;//scc[x]计入x在哪个强连通分量中
void tarjan(int x) {
    //入x时,盖戳、入栈
    dfn[x] = low[x] = ++tot;
    stk[++top] = x, instk[x] = 1;
    for (int y: e[x]) {
        if (!dfn[y]) {//若y尚未访问
            tarjan(y);
            low[x] = min(low[x], low[y]);//回x时更新low
        } else if (instk[y])//若y已访问且在栈中
            low[x] = min(low[x], dfn[y]);//在x时更新low
    }
    //离x时,收集SCC
    if (dfn[x] == low[x]) {//若x是SCC的根
        int y;
        ++cnt;
        do {
            y = stk[top--];
            instk[y] = 0;
            scc[y] = cnt;//SCC编号
            siz[cnt] += a[y];//SCC大小
        } while (y != x);
    }
}
vector<int> ne[N];
int din[N],sum[N];
int topo(){
    queue<int> q;
    for(int i=1;i<=cnt;i++){
        if(!din[i]) q.push(i),sum[i]=siz[i];
    }
    while(q.size()){
        int u=q.front();
        q.pop();
        for(auto v:ne[u]){
            sum[v]=max(sum[v],siz[v]+sum[u]);
            --din[v];
            if(din[v]==0) q.push(v);
        }
    }
    int ans=0;
    for(int i=1;i<=cnt;i++){
        ans=max(ans,sum[i]);
    }
    return ans;
}
void solve() {
    int n, m;
    cin >> n >> m;
    for (int i = 1; i <= n; i++) cin >> a[i];
    for (int i = 1; i <= m; i++) {
        int u, v;
        cin >> u >> v;
        e[u].push_back(v);
    }
    for (int i = 1; i <= n; i++) {
        if (!dfn[i]) tarjan(i);
    }
    for (int u = 1; u <= n; u++) {
        for (auto v: e[u]) {
            if (scc[v] == scc[u]) continue;
            ne[scc[u]].push_back(scc[v]);
            din[scc[v]]++;
        }
    }
    cout<<topo()<<endl;
}
signed main() {
    ios_base::sync_with_stdio(0), cin.tie(0), cout.tie(0);
//    int t;
//    cin >> t;
//    while (t--)
    solve();
    return 0;
}

5.上白泽慧音

题目链接:https://www.luogu.com.cn/problem/P1726

模板题

#include<bits/stdc++.h>
#define int long long
#define endl '\n'
using namespace std;
const int N=1e4+10;
vector<int> e[N];
int dfn[N],low[N],tot;//dfn[x]为节点第一次被访问的顺序(时间戳),low[x]为从节点x出发能访问的最早时间戳
int stk[N],instk[N],top;//标记是否在栈中
int scc[N],siz[N],cnt;//scc[x]计入x在哪个强连通分量中
void tarjan(int x) {
    //入x时,盖戳、入栈
    dfn[x] = low[x] = ++tot;
    stk[++top] = x, instk[x] = 1;
    for (int y: e[x]) {
        if (!dfn[y]) {//若y尚未访问
            tarjan(y);
            low[x] = min(low[x], low[y]);//回x时更新low
        } else if (instk[y])//若y已访问且在栈中
            low[x] = min(low[x], dfn[y]);//在x时更新low
    }
    //离x时,收集SCC
    if (dfn[x] == low[x]) {//若x是SCC的根
        int y;
        ++cnt;
        do {
            y = stk[top--];
            instk[y] = 0;
            scc[y] = cnt;//SCC编号
            siz[cnt] ++;//SCC大小
        } while (y != x);
    }
}
void solve() {
    int n, m;
    cin >> n >> m;
    int x, u, v;
    for (int i = 1; i <= m; i++) {
        cin >> u >> v >> x;
        if (x == 1) {
            e[u].push_back(v);
        } else {
            e[u].push_back(v);
            e[v].push_back(u);
        }
    }
    for (int i = 1; i <= n; i++) {
        if (!dfn[i]) tarjan(i);
    }
    vector<int> ans[N];
    for (int i = 1; i <= n; i++) {
        ans[scc[i]].push_back(i);
    }
    int ans1 = 0;
    for (int i = 1; i <= cnt; i++) {
        ans1 = max(ans1, siz[i]);
    }
    cout << ans1 << endl;
    for (int i = 1; i <= cnt; i++) {
        if (siz[i] == ans1) {
            for (auto k: ans[i]) {
                cout << k << " ";
            }
            cout << endl;
        }
    }
}
signed main() {
    ios_base::sync_with_stdio(0), cin.tie(0), cout.tie(0);
//    int t;
//    cin >> t;
//    while (t--)
    solve();
    return 0;
}

5.消息扩散

题目链接:https://www.luogu.com.cn/problem/P2002

答案就是缩点后入度为0的个数

#include<bits/stdc++.h>
#define int long long
#define endl '\n'
using namespace std;
const int N=1e5+10;
vector<int> e[N];
int dfn[N],low[N],tot;//dfn[x]为节点第一次被访问的顺序(时间戳),low[x]为从节点x出发能访问的最早时间戳
int stk[N],instk[N],top;//标记是否在栈中
int scc[N],siz[N],cnt;//scc[x]计入x在哪个强连通分量中
void tarjan(int x) {
    //入x时,盖戳、入栈
    dfn[x] = low[x] = ++tot;
    stk[++top] = x, instk[x] = 1;
    for (int y: e[x]) {
        if (!dfn[y]) {//若y尚未访问
            tarjan(y);
            low[x] = min(low[x], low[y]);//回x时更新low
        } else if (instk[y])//若y已访问且在栈中
            low[x] = min(low[x], dfn[y]);//在x时更新low
    }
    //离x时,收集SCC
    if (dfn[x] == low[x]) {//若x是SCC的根
        int y;
        ++cnt;
        do {
            y = stk[top--];
            instk[y] = 0;
            scc[y] = cnt;//SCC编号
            siz[cnt] ++;//SCC大小
        } while (y != x);
    }
}
int din[N];
void solve() {
    int n, m;
    cin >> n >> m;
    int u, v;
    for (int i = 1; i <= m; i++) {
        cin >> u >> v;
        if (u != v) e[u].push_back(v);
    }
    for (int i = 1; i <= n; i++) {
        if (!dfn[i]) tarjan(i);
    }
    for (int u = 1; u <= n; u++) {
        for (auto v: e[u]) {
            if (scc[u] == scc[v]) continue;
            din[scc[v]]++;
        }
    }
    int pp = 0;
    for (int i = 1; i <= cnt; i++) {
        if (!din[i]) pp++;
    }
    cout << pp << endl;
}
signed main() {
    ios_base::sync_with_stdio(0), cin.tie(0), cout.tie(0);
//    int t;
//    cin >> t;
//    while (t--)
    solve();
    return 0;
}

接下来是几道蓝题

P1262 间谍网络

题目链接:https://www.luogu.com.cn/problem/P1262

首先,怎么去判断可以控制所有的间谍呢(也就是判断Yes or No),缩点构建新图后,如果入度为0且该入度为0的强连通分量中所有的点都不能被控制,这样就会有间谍不能被控制,就输出No。输出YES后面的要支付的最小贿金,就把入度为0的强连通分量中的每个点最小金额相加就行。

最后就是No后面的输出,利用拓扑(拓扑中,先存入队列的点是可以被控制的强连通分量),将不能控制的强连通分量求出来,然后取最小的那个。

AC代码:

#include<bits/stdc++.h>
#define int long long
#define endl '\n'
using namespace std;
const int N=1e5+10;
vector<int> e[N];
int dfn[N],low[N],tot,a[N];//dfn[x]为节点第一次被访问的顺序(时间戳),low[x]为从节点x出发能访问的最早时间戳
int stk[N],instk[N],top;//标记是否在栈中
int scc[N],siz[N],cnt;//scc[x]计入x在哪个强连通分量中
int din[N];
int n, p;
void tarjan(int x) {
    //入x时,盖戳、入栈
    dfn[x] = low[x] = ++tot;
    stk[++top] = x, instk[x] = 1;
    for (int y: e[x]) {
        if (!dfn[y]) {//若y尚未访问
            tarjan(y);
            low[x] = min(low[x], low[y]);//回x时更新low
        } else if (instk[y])//若y已访问且在栈中
            low[x] = min(low[x], dfn[y]);//在x时更新low
    }
    //离x时,收集SCC
    if (dfn[x] == low[x]) {//若x是SCC的根
        int y;
        ++cnt;
        do {
            y = stk[top--];
            instk[y] = 0;
            scc[y] = cnt;//SCC编号
            siz[cnt] ++;//SCC大小
        } while (y != x);
    }
}
vector<int> ans[N];
vector<int> ne[N];
int book[N],st[N];
int topo(){
    int t=1e18;
    queue<int> q;
    for(int i=1;i<=cnt;i++){
        if(book[i]) q.push(i),st[i]=1;
    }
    while(q.size()){
        int k=q.front();
        q.pop();
        for(auto v:ne[k]){
            din[v]--;
            if(din[v]==0){
                q.push(v);
                st[v]=1;
            }
        }
    }
    for(int i=1;i<=cnt;i++){
        if(!st[i]){
            t=min(t,ans[i][0]);
        }
    }
    return t;
}
void solve() {
    cin >> n >> p;
    for (int i = 1; i <= p; i++) {
        int x, w;
        cin >> x >> w;
        a[x] = w;
    }
    int r;
    cin >> r;
    while (r--) {
        int u, v;
        cin >> u >> v;
        e[u].push_back(v);
    }
    for (int i = 1; i <= n; i++) if (!dfn[i]) tarjan(i);
    for (int i = 1; i <= n; i++) {
        ans[scc[i]].push_back(i);
    }
    for (int i = 1; i <= n; i++) {
        if (a[i]) {
            book[scc[i]] = 1;
        }
    }
    for (int u = 1; u <= n; u++) {
        for (int v: e[u]) {
            if (scc[v] != scc[u]) {
                din[scc[v]]++;
                ne[scc[u]].push_back(scc[v]);
            }
        }
    }
    int ans1 = 0;
    int fl = 0;
    for (int i = 1; i <= cnt; i++) {
        if (din[i] == 0) {
            int mn = 1e18, f = 0;
            for (auto k: ans[i]) {
                if (!a[k]) continue;
                else {
                    f = 1;
                    mn = min(mn, a[k]);
                }
            }
            if (f) {
                ans1 += mn;
            } else {
                fl = 1;
            }
        }
    }
    if (fl) {
        cout << "NO" << endl;
        cout << topo() << endl;
    } else {
        cout << "YES" << endl;
        cout << ans1 << endl;
    }
}
signed main() {
    ios_base::sync_with_stdio(0), cin.tie(0), cout.tie(0);
//    int t;
//    cin >> t;
//    while (t--)
    solve();
    return 0;
}

P2169 正则表达式

题目链接:https://www.luogu.com.cn/problem/P2169

这题就是缩点后最短路,很简单

AC代码:

#include<bits/stdc++.h>
#define int long long
#define endl '\n'
using namespace std;
const int N=2e5+10,inf=0x3f3f3f3f;
struct node{
    int v,w;
};
vector<node> e[N];
int dfn[N],low[N],tot,a[N];//dfn[x]为节点第一次被访问的顺序(时间戳),low[x]为从节点x出发能访问的最早时间戳
int stk[N],instk[N],top;//标记是否在栈中
int scc[N],siz[N],cnt;//scc[x]计入x在哪个强连通分量中
int din[N];
int n, m;
void tarjan(int x) {
    //入x时,盖戳、入栈
    dfn[x] = low[x] = ++tot;
    stk[++top] = x, instk[x] = 1;
    for (auto k: e[x]) {
        int y=k.v;
        if (!dfn[y]) {//若y尚未访问
            tarjan(y);
            low[x] = min(low[x], low[y]);//回x时更新low
        } else if (instk[y])//若y已访问且在栈中
            low[x] = min(low[x], dfn[y]);//在x时更新low
    }
    //离x时,收集SCC
    if (dfn[x] == low[x]) {//若x是SCC的根
        int y;
        ++cnt;
        do {
            y = stk[top--];
            instk[y] = 0;
            scc[y] = cnt;//SCC编号
            siz[cnt] ++;//SCC大小
        } while (y != x);
    }
}
vector<node> ne[N];
struct node1 {
    int x, y;
};
struct cmp {bool operator()(const node1 &a, const node1 &b) const {return a.x > b.x;}};
priority_queue<node1,vector<node1>,cmp> q;
int d[N],vis[N];
void dj(int s) {
    for (int i = 1; i <= cnt; i++) {
        d[i] = inf;
    }
    d[s] = 0;
    q.push({0, s});
    while (q.size()) {
        auto t = q.top();
        q.pop();
        int u = t.y;
        if (vis[u]) continue;
        vis[u] = 1;
        for (auto k: ne[u]) {
            int v = k.v, w = k.w;
            if (d[v] > d[u] + w) {
                d[v] = d[u] + w;
                q.push({d[v], v});
            }
        }
    }
}
void solve() {
    cin >> n >> m;
    for (int i = 1; i <= m; i++) {
        int u, v, w;
        cin >> u >> v >> w;
        e[u].push_back({v, w});
    }
    for (int i = 1; i <= n; i++) if (!dfn[i]) tarjan(i);
    for (int u = 1; u <= n; u++) {
        for (auto k: e[u]) {
            int v = k.v, w = k.w;
            if (scc[u] != scc[v]) {
                ne[scc[u]].push_back({scc[v], w});
            }
        }
    }
    dj(scc[1]);
    cout << d[scc[n]] << endl;
}
signed main() {
    ios_base::sync_with_stdio(0), cin.tie(0), cout.tie(0);
//    int t;
//    cin >> t;
//    while (t--)
    solve();
    return 0;
}

P2194 HXY烧情侣

题目链接:https://www.luogu.com.cn/problem/P2194

最小花费就求出每个强连通分量中的最小的点的汽油金额,方案数就是每个强连通分量中的最小的个数的乘积

AC代码:

#include<bits/stdc++.h>
#define int long long
#define endl '\n'
using namespace std;
const int N=2e5+10,inf=0x3f3f3f3f,mod=1e9+7;
vector<int> e[N];
int dfn[N],low[N],tot,a[N];//dfn[x]为节点第一次被访问的顺序(时间戳),low[x]为从节点x出发能访问的最早时间戳
int stk[N],instk[N],top;//标记是否在栈中
int scc[N],siz[N],cnt;//scc[x]计入x在哪个强连通分量中
int din[N];
int n, m;
void tarjan(int x) {
    //入x时,盖戳、入栈
    dfn[x] = low[x] = ++tot;
    stk[++top] = x, instk[x] = 1;
    for (int y: e[x]) {
        if (!dfn[y]) {//若y尚未访问
            tarjan(y);
            low[x] = min(low[x], low[y]);//回x时更新low
        } else if (instk[y])//若y已访问且在栈中
            low[x] = min(low[x], dfn[y]);//在x时更新low
    }
    //离x时,收集SCC
    if (dfn[x] == low[x]) {//若x是SCC的根
        int y;
        ++cnt;
        do {
            y = stk[top--];
            instk[y] = 0;
            scc[y] = cnt;//SCC编号
//            siz[cnt]++;
            siz[cnt] =min(siz[cnt],a[y]);//SCC大小
        } while (y != x);
    }
}
vector<int> ne[N];
vector<int> ans[N];
void solve() {
    cin >> n;
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
    }
    for (int i = 1; i <= n; i++) {
        siz[i] = 1e9;
    }
    cin >> m;
    while (m--) {
        int u, v;
        cin >> u >> v;
        e[u].push_back(v);
    }
    for (int i = 1; i <= n; i++) {
        if (!dfn[i]) tarjan(i);
    }
    for (int u = 1; u <= n; u++) {
        for (int v: e[u]) {
            if (scc[u] != scc[v]) {
                din[scc[v]]++;
            }
        }
    }
    for (int i = 1; i <= n; i++) {
        ans[scc[i]].push_back(i);
    }
    int ans1 = 0;
    int ans2 = 1;
    for (int i = 1; i <= cnt; i++) {
        ans1 += siz[i];
        int t = 0;
        for (auto k: ans[i]) {
            if (a[k] == siz[i]) t++;
        }
        ans2 = ((ans2 % mod) * (t % mod)) % mod;
    }
    cout << ans1 << " " << ans2 << endl;
}
signed main() {
    ios_base::sync_with_stdio(0), cin.tie(0), cout.tie(0);
//    int t;
//    cin >> t;
//    while (t--)
    solve();
    return 0;
}

P2812 校园网络【[USACO]Network of Schools加强版】

题目链接:https://www.luogu.com.cn/problem/P2812

第一行一个整数,表示至少选几所学校作为共享软件的母机,能使每所学校都可以用上。缩点后,也就是入度为0的个数

第二行一个整数,表示至少要添加几条线路能使任意一所学校作为母机都可以使别的学校使用上软件。也就是max(入度为0的个数,出度为0的个数)

注意要特判一下强连通分量的个数为1的时候

AC代码:

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e5+10;
int n,m;
vector<int> e[N];
int dfn[N],low[N],tot;//dfn[x]为节点第一次被访问的顺序(时间戳),low[x]为从节点x出发能访问的最早时间戳
int stk[N],instk[N],top;//标记是否在栈中
int scc[N],siz[N],cnt;//scc[x]计入x在哪个强连通分量中
int din[N],dout[N];
void tarjan(int x) {
    //入x时,盖戳、入栈
    dfn[x] = low[x] = ++tot;
    stk[++top] = x, instk[x] = 1;
    for (int y: e[x]) {
        if (!dfn[y]) {//若y尚未访问
            tarjan(y);
            low[x] = min(low[x], low[y]);//回x时更新low
        } else if (instk[y])//若y已访问且在栈中
            low[x] = min(low[x], dfn[y]);//在x时更新low
    }
    //离x时,收集SCC
    if (dfn[x] == low[x]) {//若x是SCC的根
        int y;
        ++cnt;
        do {
            y = stk[top--];
            instk[y] = 0;
            scc[y] = cnt;//SCC编号
            ++siz[cnt];//SCC大小
        } while (y != x);
    }
}
signed main() {
    cin >> n;
    for(int i=1;i<=n;i++){
        while(1){
            cin>>m;
            if(m==0){
                break;
            }
            e[i].push_back(m);
        }
    }
    for (int i = 1; i <= n; i++)//可能不连通
        if (!dfn[i]) tarjan(i);
    for(int i=1;i<=n;i++){//scc缩点
        for(auto k:e[i]){
            if(scc[i]!=scc[k]){
                dout[scc[i]]++;
                din[scc[k]]++;
            }
        }
    }
    int a,b=0;
    for(int i=1;i<=cnt;i++){
        if(!din[i]) a++;
        if(!dout[i]) b++;
    }
    if(cnt==1){
        cout<<1<<endl<<0<<endl;
    }
    else
    cout<<a<<endl<<max(a,b)<<endl;
    return 0;
}

P3627 [APIO2009] 抢掠计划

题目链接:https://www.luogu.com.cn/problem/P3627

题目大致目的:缩点后跑一个最长路

首先这个题为点权图,所以先将点权变成边权,然后将边权变为负数,用spfa跑最短路,就可以求最长路了

AC代码:

#include<bits/stdc++.h>
#define int long long
#define endl '\n'
using namespace std;
const int N=5e5+10,inf=0x3f3f3f3f,mod=1e9+7;
struct node{
    int v,w;
};
vector<int> e[N];
int dfn[N],low[N],tot,a[N];//dfn[x]为节点第一次被访问的顺序(时间戳),low[x]为从节点x出发能访问的最早时间戳
int stk[N],instk[N],top;//标记是否在栈中
int scc[N],siz[N],cnt;//scc[x]计入x在哪个强连通分量中
int din[N];
int n, m, tmp;
void tarjan(int x) {
    //入x时,盖戳、入栈
    dfn[x] = low[x] = ++tot;
    stk[++top] = x, instk[x] = 1;
    for (int y: e[x]) {
        if (!dfn[y]) {//若y尚未访问
            tarjan(y);
            low[x] = min(low[x], low[y]);//回x时更新low
        } else if (instk[y])//若y已访问且在栈中
            low[x] = min(low[x], dfn[y]);//在x时更新low
    }
    //离x时,收集SCC
    if (dfn[x] == low[x]) {//若x是SCC的根
        int y;
        ++cnt;
        do {
            y = stk[top--];
            instk[y] = 0;
            scc[y] = cnt;//SCC编号
            siz[cnt] += a[y];//SCC大小
        } while (y != x);
    }
}
vector<int> ans[N];
vector<node> ne[N];
int ed[N],d[N],vis[N];
queue<int> q;
void spfa(int s) {
    for (int i = 1; i <= tmp; i++) {
        d[i] = inf;
    }
    d[s] = 0;
    vis[s] = 1;
    q.push(s);
    while (q.size()) {
        int u = q.front();
        q.pop();
        vis[u] = 0;
        for (auto k: ne[u]) {
            int v = k.v, w = k.w;
            if (d[v] > d[u] + w) {
                d[v] = d[u] + w;
                if (!vis[v]) q.push(v), vis[v] = 1;
            }
        }
    }
}
void solve() {
    cin >> n >> m;
    while (m--) {
        int u, v;
        cin >> u >> v;
        e[u].push_back(v);
    }
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
        a[i] = -1 * a[i];
    }
    for (int i = 1; i <= n; i++) if (!dfn[i]) tarjan(i);
    for (int u = 1; u <= n; u++) {
        for (int v: e[u]) {
            if (scc[u] != scc[v]) {
                ne[scc[u]].push_back({scc[v], siz[scc[u]]});
            }
        }
    }
    int s, p;
    cin >> s >> p;
    tmp = cnt;
    for (int i = 1; i <= p; i++) {
        cin >> ed[i];
        ne[scc[ed[i]]].push_back({++tmp, siz[scc[ed[i]]]});
    }
    spfa(scc[s]);
    int ans1 = 0;
    for (int i = cnt + 1; i <= tmp; i++) {
        ans1 = max(-1 * d[i], ans1);
    }
    cout << ans1 << endl;
}
signed main() {
    ios_base::sync_with_stdio(0), cin.tie(0), cout.tie(0);
//    int t;
//    cin >> t;
//    while (t--)
    solve();
    return 0;
}

接下来的一点点题就打算后面无聊的时候再做了,现在要去刷Tarjan割点了。