首页 > 其他分享 >Tarjan缩点题单 刷题题解

Tarjan缩点题单 刷题题解

时间:2024-10-13 16:24:09浏览次数:1  
标签:Tarjan int 题解 ++ cnt 点题 dfn low instk

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

标签:Tarjan,int,题解,++,cnt,点题,dfn,low,instk
From: https://www.cnblogs.com/Cakefish/p/18462502

相关文章

  • 【算法题解记录】_1
    目录【算法题解记录】_1leetcode_115题【算法题解记录】_1还是干这个事了,要吃饭嘛,决定把在解题或者解完题进一步优化的时候,发现的有意思的地方记录一下leetcode_115题动态规划解的字符串匹配,矩阵全部都遍历,用时比较长,发现排在网友们的末尾纸上画了一下矩阵,行数代表被匹配串s......
  • 系统开发基础错题解析二【软考】
    目录前言1.人机界面设计2.架构设计2.1管道过滤器体系2.2仓库风格3.软件测试相关概念4.白盒测试用例4.14.25.测试分类与阶段任务划分6.软件维护类型7.软件质量保证8.软件过程改进前言本文专门用来记录本人在做软考中有关系统开发基础的错题,我始终认为教学相长是最快......
  • 数学题解报告
    TeamWork题意:求\(\sum_{i=1}^n\dbinom{n}{i}i^k\)\(n<=1e9,k<=5e3\)推式子\[\begin{aligned}记f_{n,k}&=\sum_{i=1}^n\dbinom{n}{i}i^k\\&=\sum_{i=1}^n\left[\dbinom{n-1}{i-1}+\dbinom{n-1}{i}\right]i^k\\&=\sum_{i=1}^n\d......
  • 神奇的幻方 NOIP 2015 题解
    描述幻方是一种很神奇的 N×N 矩阵:它由数字 1,2,3,⋯⋯,N×N 构成,且每行、每列及两条对角线上的数字之和都相同。当 N 为奇数时,我们可以通过下方法构建一个幻方:首先将 1 写在第一行的中间。之后,按如下方式从小到大依次填写每个数 K(K=2,3,⋯,N×N) :若 (K−1)......
  • Android12.0 需求开发篇+问题解决篇之IPC socket通信
    1.需求描述        应用组C程序客户端和Android系统层Java服务端进行通信需求,这里其实在Android系统下IPC的方式有很多,像Binder作为Android特有的跨进程通信,但是应用组的同事之前是非Android系统下进行应用开发,使用的都是socket这种通用IPC通信。这里为兼容应用组代码......
  • 题解:牛客小白月赛102(A - C)
    A序列中的排列题意:每次给你两个正整数\(n,k\),并给你一段长度为\(n\)的序列。(所有输入均为小于等于100的正整数)问:原序列中是否存在子序列,使得这个子序列是\(k\)的排列子序列:某个序列的子序列是从最初序列通过去除某些元素但不破坏余下元素的相对位置(在前或在后)而形成的......
  • BUUCTF_MISC题解析(7)
    7.wireshark下载文件发现里面是一个pcap格式的文件。而pcap格式就是网络分析工具保存的网络数据包,是捕获的从网卡发送或者接收的每一个数据包的离线网络流量。 在wireshark官网上下载wireshark,wireshark是网络封包分析工具。将文件用wireshark打开,发现有三个部分,上半部分绿......
  • P9020 [USACO23JAN] Mana Collection P 题解
    P9020[USACO23JAN]ManaCollectionP题解首先考虑对于长为\(d\les\)的最优路径,最优的方法一定是先在起点等\(s-d\)秒再走以确保收集到的最大。\(n\le18\)我们显然考虑状压dp。考虑最大法力值难以计算,正难则反,考虑使未被选择的最小。于是我们设\(dp_{sta,i}\)表示状......
  • Project Euler 728 题解
    Problem728CircleofCoins得到Wallbreaker5th的指导。\(F\)就是求这些环上区间(记为\(A\))的异或线性基大小。令\(A'_i\getsA_i\oplusA_{i+1}\)。现在求\(\langA'\rang\)的线性基。如果可能从全黑和全白间转换,那么\(\dim\langA'\rang=\langA\rang-1\),否则不\(-1......
  • [ABC227H] Eat Them All 题解
    唐唐题。思路容易发现,我们只要知道了一条边总共经过了多少次(不计方向),我们就可以跑欧拉回路。自己手玩一下,发现只要枚举四个变量,其他的都可以用方程解出来。求完之后,还需要判一下联通性。实际效率是很快的,远远跑不满。时间复杂度:\(O(a_i^4)\)。Code#include<bits/stdc++.h......