为什么缩点之后用 spfa 过不了?

P2272 [ZJOI2007] 最大半连通子图

Richard_H @ 2022-10-11 17:36:20

这个题,因为强连通分量肯定是一个半连通图,所以可以把一坨点缩成一个点,点权为这一坨点的个数,然后就建一个新的图出来,用 set 避免重边,然后搞一个超级源点跑 spfa 。结果只能过 5 个点,卡在了第 6 个点,把 spfa 那部分改成拓扑之后就又对了,这是为什么?

下面是我提交的过了 5 个点的代码 ↓ ↓ ↓

#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+5;
int n, m, x, tim = 0, idx = 0, dis[N], clr[N], dfn[N], dist[N], low[N], cnt[N], u, v, ans = 0, ans_num;
bool vis[N], st[N];
stack<int> s;
queue<int> q;
vector<int> lnk[N];
set< pair<int, int> > exl[N];

void tarjan(int x) {
    vis[x] = true;
    s.push(x);
    dfn[x] = low[x] = ++tim;

    for(auto item : lnk[x]) {
        if(!dfn[item]) {
            tarjan(item);
            low[x] = min(low[x], low[item]);
        } else if(vis[item]) {
            low[x] = min(low[x], dfn[item]);
        }
    }

    if(dfn[x] == low[x]) {
        int op;
        idx ++;
        do{
            op = s.top();
            s.pop();
            clr[op] = idx;
            dis[idx] ++;
            vis[op] = false;
        } while(op != x);
    }
}

void spfa(){
    memset(dist, -1, sizeof dist);
    q.push(0);
    dist[0] = 0;
    cnt[0] = 1;
    while(!q.empty()) {
        int op = q.front();
        q.pop();
        st[op] = false;
        for(auto item : exl[op]) {
            if(dist[item.first] < dist[op] + item.second) {
                dist[item.first] = dist[op] + item.second;
                cnt[item.first] = cnt[op];
                if(!st[item.first]) {
                    q.push(item.first);
                    st[item.first] = true;
                }
            } else if(dist[item.first] == dist[op] + item.second) cnt[item.first] = (cnt[item.first] + cnt[op]) % x;
        }
    }
}

int main(){
    scanf("%d%d%d", &n, &m, &x);
    for(int i = 1; i <= m; i ++ ) {
        scanf("%d%d", &u, &v);
        lnk[u].push_back(v);
    }
    for(int i = 1; i <= n; i ++ ) {
        if(!dfn[i]) {
            tarjan(i);
        }
    }
    // printf("%d\n", idx);
    // for(int i = 1; i <= n; i ++ ) {
        // printf("%d %d\n", i, clr[i]);
    // }
    for(int i = 1; i <= n; i ++ ) {
        for(auto item : lnk[i]) {
            if(clr[i] != clr[item]) {
                exl[clr[i]].insert({clr[item], dis[clr[item]]});
                // printf("%d %d %d\n", clr[i], clr[item], dis[clr[item]]);
            }
        }
    }
    for(int i = 1; i <= idx; i ++ ) {
        exl[0].insert({i, dis[i]});
        // printf("0 %d %d\n", i, dis[i]);
    }
    spfa();
    for(int i = 1; i <= idx; i ++ ) {
        // printf("%d %d %d\n", i, dist[i], cnt[i]);
        if(ans < dist[i]) {
            ans = dist[i];
            ans_num = cnt[i];
        } else if(ans == dist[i]) ans_num = (ans_num + cnt[i]) % x;
    }
    printf("%d\n%d", ans, ans_num);
    return 0;
}

by so_find_skind @ 2022-10-11 17:59:44

这道题估计是spfa的三转路克星


|