洛谷 P1073 最优贸易(图论)

题意

求出从图上节点1到 N ( N ≤ 1 0 5 ) N(N \leq 10^5) N(N105)所经过的路径中,差价最大的点对,其中高价的点对必须在低价点对的前面(高点到低点有一条正向路径在1到N的路径上)。

解题思路

要求的点必须在路径上,那么我就想到用拓扑排序去做,但是因为这个图可能会有环,所以需要用强连通分量缩点。缩点以后求出每个连通分量的最高价和最低价,再利用DP求出路径中最高价和后面的最低价之间差的最大值就是答案了。一开始看题的时候漏了是 1 → N 1 \to N 1N的路径了,以为只要是高点在低点前面就可以了,导致WA了好几次。

求强联通分量有好几种算法,Tarjan和Kosaraju,我比较喜欢用Kosaraju,虽然后者时间复杂度要高一个常数,但是写起来简单也容易理解。

缩点以后的图为了防止多次加入节点,我用了std::set作为邻接表的实现,所以时间复杂度会多一个 log ⁡ \log log,但是影响不大,还省去了判重的步骤。

看了别人的题解好像有两次最短路的?管他呢,自己思路想清楚了才是最重要的。

时间复杂度

O ( N ) O(N) O(N)

代码

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

using namespace std;

typedef unsigned long long ll;

const int INF = 2147483647;
const int INF2 = 0x3f3f3f3f;
const ll INF64 = 0x3f3f3f3f3f3f3f3f;
const double PI = 3.14159265358979323846;
const int MOD = (int)(1e9) + 7;
const ll TARGET = (1LL << 32) - 1LL;

template <typename T>
inline T read() {
    T X = 0, w = 0;
    char ch = 0;
    while (!isdigit(ch)) {
        w |= ch == '-';
        ch = getchar();
    }
    while (isdigit(ch)) X = (X << 3) + (X << 1) + (ch ^ 48), ch = getchar();
    return w ? -X : X;
}

struct Point {
    int x;
    int y;
    bool operator<(const Point &other) { return x * other.y < y * other.x; }
};

const int MAXN = 100005;
int n, m, k;
int w, h;

int CASE = 1;
vector<int> graph[MAXN];
vector<int> graphrev[MAXN];
set<int> littlegraph[MAXN];
int vis[MAXN];
int scc[MAXN];
stack<int> revOrder;
int cc = 1;
int ans = 0;
int fanin[MAXN];
int cost[MAXN];
int maxx[MAXN], minn[MAXN];
int dp[MAXN];

void dfs1(int x) {
    vis[x] = true;
    for (auto v : graphrev[x]) {
        if (!vis[v]) dfs1(v);
    }
    revOrder.push(x);
}

void dfs2(int x) {
    scc[x] = cc;
    vis[x] = true;
    maxx[cc] = max(maxx[cc], cost[x]);
    minn[cc] = min(minn[cc], cost[x]);
    for (auto v : graph[x]) {
        if (!vis[v]) dfs2(v);
    }
}

void dfs(int x, int mx) {
    ans = max(ans, max(maxx[x], mx) - minn[x]);
    for (auto v : littlegraph[x]) {
        dfs(v, max(mx, maxx[x]));
    }
}

int main() {
#ifdef LOCALLL
    freopen("in", "r", stdin);
    freopen("out", "w", stdout);
#endif
    scanf("%d %d", &n, &m);
    for (int i = 1; i <= n; i++) {
        cost[i] = read<int>();
    }
    int a, b, c;
    for (int i = 0; i < m; i++) {
        a = read<int>(), b = read<int>();
        c = read<int>();
        graph[a].push_back(b);
        graphrev[b].push_back(a);
        if (c == 2) {
            graph[b].push_back(a);
            graphrev[a].push_back(b);
        }
    }
    // Kosaraju强连通分量缩点
    for (int i = 1; i <= n; i++) {
        if (!vis[i]) dfs1(i);
    }
    memset(vis, 0, sizeof(vis));
    memset(minn, 0x3f, sizeof(minn));
    while (!revOrder.empty()) {
        auto v = revOrder.top();
        revOrder.pop();
        if (!vis[v]) {
            dfs2(v);
            cc++;
        }
    }
    // 建立缩点以后的图
    for (int i = 1; i <= n; i++) {
        for (auto v : graph[i]) {
            if (scc[i] != scc[v]) {
                littlegraph[scc[i]].insert(scc[v]);
            }
        }
    }
    for (int i = 1; i < cc; i++) {
        for (auto v : littlegraph[i]) {
            fanin[v]++;
        }
    }
    queue<int> Q;
    for (int i = 1; i < cc; i++) {
        if (fanin[i] == 0) Q.push(i);
    }
    memset(vis, 0, sizeof(vis));
    vis[scc[1]] = true;
    dp[scc[1]] = maxx[scc[1]] - minn[scc[1]];
    // 拓扑排序的同时DP
    while (!Q.empty()) {
        int v = Q.front();
        Q.pop();
        for (auto u : littlegraph[v]) {
            fanin[u]--;
            if (vis[v]) {
                vis[u] = true;
                minn[u] = min(minn[u], minn[v]);
                dp[u] = max(dp[u], max(maxx[u] - minn[u], dp[v]));
            }
            if (!fanin[u]) Q.push(u);
        }
    }

    printf("%d", dp[scc[n]]);

    return 0;
}

你可能感兴趣的:(图论,洛谷)