Codeforces Round 911 (Div. 2) E题

主要由三部分组成:Tarjan算法,拓扑排序(topsort),以及动态规划(dp)。

  1. Tarjan算法:这是一种用于在图中查找强连通分量或割点的算法。在这段代码中,Tarjan算法被用于找出图中的强连通分量。每个强连通分量被视为一个节点,节点的权值为该强连通分量中所有节点的权值之和。

  2. 拓扑排序:拓扑排序是对有向无环图(DAG)进行排序的算法,它会按照依赖关系进行排序。在这段代码中,拓扑排序被用于确定强连通分量(视为节点)之间的依赖关系,从而确定最优的遍历顺序。

  3. 动态规划:动态规划是一种通过把原问题分解为相互重叠的子问题来求解问题的方法。在这段代码中,动态规划被用于在给定的依赖关系下,找出权值最大的路径

对于一个路径来说,如果走入了一个强连通分量,无论如何都要走完这个强连通分量的点

缩点之后就对有向无环图(DAG)进行拓扑排序 + dp

代码的状态转移方程可以表示为:

  • 对于每个节点v,我们遍历其所有前驱节点u

  • 如果f[u] > f[v],那么我们更新f[v] = f[u],并且g[v] = g[u]。这表示如果从根节点到节点u的路径上能集聚的点数多于从根节点到节点v的路径,那么我们就选择从根节点到节点u的路径,并且更新从根节点到节点v的路径的最小权值为从根节点到节点u的路径的最小权值。

  • 如果f[u] == f[v],那么我们更新g[v] = min(g[u], g[v])。这表示如果从根节点到节点u的路径上能集聚的点数等于从根节点到节点v的路径,那么我们就选择这两条路径中权值最小的那条。

所以,状态转移方程可以表示为:

  • f[v] = max(f[u]),其中uv的所有前驱节点。
  • g[v] = min(g[u]),其中u是使得f[u] = f[v]的所有节点。

以下是代码

#include <iostream>
#include <cstring>
#include <stack>
#include <algorithm>
#include <queue>
#include <vector>
#include <cmath>
#include <set>

#define mem(a,b) memset(a,b,sizeof(a))
#define rep(a, b, c) for (a = b; a <= c; a++)
#define int long long
using namespace std;

typedef double db;
typedef pair<int,int> pii;
const int N = 200010, M = 200010 * 2;
const int inf = 3e18;

int n,m;
int h[N],H[N],ne[M],e[M],idx; // 领链表

int w[N];

int dfn[N],low[N],id[N],timestamp,scc_cnt; 
stack <int> stk;
bool isStk[N]; 
int sz[N],din[N],val[N];

void add(int h[],int a,int b){
    e[idx] = b;
    ne[idx] = h[a];
    h[a] = idx ++;
}
void tarjan(int u){
    dfn[u] = low[u] = ++ timestamp;
    stk.push(u); isStk[u] = true;

    for (int i = h[u] ; ~i ; i = ne[i]){
        int x = e[i];
        // 后向边
        if (!dfn[x]){
            tarjan(x);
            low[u] = min(low[u],low[x]);
        }
        // 前向边 
        else if (isStk[x]){
            low[u] = min(low[u],dfn[x]);
        }
    }

    // 如果该点是连通分量的顶点
    if (dfn[u] == low[u]){
        int y;
        scc_cnt ++;

        do {
            y = stk.top(); stk.pop();
            isStk[y] = false;
            id[y] = scc_cnt;
            sz[scc_cnt] ++ ;
            val[scc_cnt] += w[y];
        } while (y != u);
    }
}

int f[N]; // f[i] 从根到i这个点所能集聚的最多的点
int g[N]; // g[i] f[i]max的前提现 最小的g[i]

void topsort(int h[]){
    queue <int> q;

    int i;
    rep(i,1,scc_cnt){
        if (!din[i]){
            q.push(i);
            g[i] = 0;
        }
    }

    while (q.size()){
        int u = q.front(); q.pop();
        f[u] += sz[u];
        g[u] += val[u];

        for (i = h[u]; ~i; i = ne[i]){
            int v = e[i];
            
            if (f[u] > f[v]){
                f[v] = f[u];
                g[v] = g[u];
            }else if (f[u] == f[v]){
                g[v] = min(g[u],g[v]);
            }

            din[v] --;
            if (din[v] == 0) q.push(v);
        }
    }

    int ans_long = 0, ans_val = inf;
    rep(i,1,scc_cnt){
        if (ans_long < f[i]){
            ans_long = f[i];
            ans_val = g[i];
        }else if(ans_long == f[i]){
            ans_val = min(ans_val,g[i]);
        }
    }

    cout << ans_long << ' ' << ans_val << endl;
}


void solve(){
    int i;
    {
        cin >> n >> m;
        timestamp = scc_cnt = idx = 0;
        while (stk.size()) stk.pop();

        int i;
        rep(i,1,n){
            cin >> w[i];

            // init
            H[i] = h[i] = -1;

            dfn[i] = low[i] = id[i] = 0;
            isStk[i] = 0;
            din[i] = val[i] = sz[i] = 0;
            f[i] = 0;
            g[i] = inf;
        }

        while (m --){
            int a,b;
            cin >> a >> b;
            add(h,a,b);
        }

        rep(i,1,n){
            if (!dfn[i]){
                tarjan(i);
            }
        }
    }

    // 去重用的
    set <int> edge;

    int u;
    rep(u,1,n){
        for (int i = h[u]; ~i; i = ne[i]){
            int v = e[i];

            int A = id[u], B = id[v];
            if (A != B && !edge.count(A * N + B)){
                edge.insert(A * N + B);
                add(H,A,B);
                din[B] ++;
            }
        }
    }

    topsort(H);

}

signed main(){
    cin.tie(0);

    int Gold_age = 1;
    cin >> Gold_age;    

    while (Gold_age --){
        solve();
    }

    system("pause");
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

OneGrave

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值