Tarjan縮點題單 刷題題解

糕手小鱼發表於2024-10-13

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割點了。

相關文章