P1074最优贸易(Tarjan+拓扑排序(dp))

题目链接:https://ac.nowcoder.com/acm/contest/1055/C

题目大意:

给定一个有向图,有向图中的每个点有一个权值,你需要在图中选择一条道路,道路上的最大权值减去最小权值最大。

思路:

书上给的思路是建立正图和反图,跑两边spfa,但是我拿到题目的第一个印象就是tarjan缩点然后dp,因为是有向图,所以可以缩点成为DAG,那么成为DAG之后就好办了,dp[i]表示以i为结尾的最大利润,我们只需要做一遍拓扑排序即可进行转移(因为有些点不能到达i点,这是一个有向无环图)

#include <bits/stdc++.h>
#define INF 1000000000
using namespace std;
const int N = 100100;
const int M = 1000500;
int n, m, val[N], cnt, dfn[N], low[N], tot, U[N], V[N], ind[N], ins[N], vis[N];
int scc, bel[N], Max[N], Min[N], List[N], ans, dp[N], sta[N], top, z[N];
struct edge {
  int v; edge *next;
}pool[M * 6], *head[N], *h[N], *hh[N];
inline void addd(int u, int v) {
  edge *p = &pool[++cnt];
  p->v = v, p->next = hh[u], hh[u] = p;
}
inline void add(int u, int v) {
  edge *p = &pool[++cnt];
  p->v = v, p->next = h[u], h[u] = p;
}
inline void addedge(int u, int v) {
  edge *p = &pool[++cnt];
  p->v = v, p->next = head[u], head[u] = p;
}
inline void tarjan(int u) {
  dfn[u] = low[u] = ++tot; ins[u] = 1, sta[++top] = u;
  for(edge *p = head[u]; p; p = p->next) {
    int v = p->v;
    if(!dfn[v]) tarjan(v), low[u] = min(low[u], low[v]);
    else if(ins[v]) low[u] = min(low[u], dfn[v]);
  } if(low[u] == dfn[u]) {
    ++scc;
    while(sta[top + 1] != u) {
      Max[scc] = max(Max[scc], val[sta[top]]);
      Min[scc] = min(Min[scc], val[sta[top]]);
      bel[sta[top]] = scc; ins[sta[top--]] = 0;
    }
  }
}
inline void toposort() {
  tot = 0; queue <int> Q;
  while(!Q.empty()) Q.pop();
  for(int i = 1; i <= scc; i++)
    if(!ind[i]) Q.push(i);
  while(!Q.empty()) {
    int u = Q.front(); Q.pop();
    List[++tot] = u;
    for(edge *p = h[u]; p; p = p->next) {
      int v = p->v; ind[v]--;
      if(ind[v] == 0) Q.push(v);
    }
  }
}
inline void dfs(int u) {
  if(vis[u]) return ; vis[u] = 1;
  for(edge *p = hh[u]; p; p = p->next) dfs(p->v);
}
int main() {
  scanf("%d %d", &n, &m);
  for(int i = 1; i <= n; i++) scanf("%d", &val[i]);
  for(int i = 1; i <= n; i++) Min[i] = INF, Max[i] = -INF;
  for(int i = 1; i <= m; i++) {
    scanf("%d %d %d", &U[i], &V[i], &z[i]);
    addedge(U[i], V[i]);
    if(z[i] == 2) addedge(V[i], U[i]);
  }
  for(int i = 1; i <= n; i++)
    if(!dfn[i]) tarjan(i);
  for(int i = 1; i <= m; i++) {
    int x = bel[U[i]], y = bel[V[i]];
    if(x != y) { add(x, y), ind[y]++, addd(y, x);
      if(z[i] == 2) add(y, x), ind[x]++, addd(x, y);
    }
  } toposort(); dfs(bel[n]);
  for(int i = 1; i <= scc; i++) dp[i] = Min[i];
  for(int i = 1; i <= scc; i++) {
    int u = List[i];
    if(vis[u]) ans = max(ans, Max[u] - dp[u]);
    for(edge *p = h[u]; p; p = p->next) {
      int v = p->v; dp[v] = min(dp[v], dp[u]);
    }
  }
  printf("%d\n", ans);
  return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值