[NOIP2009 T3]最优贸易(分层图spfa / 两次spfa / tarjan + topo)

题目

描述

C C 国有 n 个大城市和 m m 条道路,每条道路连接这 n 个城市中的某两个城市。任意两个城市之间最多只有一条道路直接相连。这 m m 条道路中有一部分为单向通行的道路,一部分为双向通行的道路,双向通行的道路在统计条数时也计为 1 条。
C C 国幅员辽阔,各地的资源分布情况各不相同,这就导致了同一种商品在不同城市的价格不一定相同。但是,同一种商品在同一个城市的买入价和卖出价始终是相同的。
商人阿龙来到 C 国旅游。当他得知同一种商品在不同城市的价格可能会不同这一信息之后,便决定在旅游的同时,利用商品在不同城市中的差价赚回一点旅费。设 C C 国 n 个城市的标号从 1n ,阿龙决定从 1 1 号城市出发,并最终在 n 号城市结束自己的旅行。在旅游的过程中,任何城市可以重复经过多次,但不要求经过所有 n n 个城市。阿龙通过这样的贸易方式赚取旅费:他会选择一个经过的城市买入他最喜欢的商品――水晶球,并在之后经过的另一个城市卖出这个水晶球,用赚取的差价当做旅费。由于阿龙主要是来 C 国旅游,他决定这个贸易只进行最多一次,当然,在赚不到差价的情况下他就无需进行贸易。
现在给出 n n 个城市的水晶球价格, m 条道路的信息(每条道路所连接的两个城市的编号以及该条道路的通行情况)。请你告诉阿龙,他最多能赚取多少旅费。

输入

第一行包含 2 2 个正整数 n m m ,中间用一个空格隔开,分别表示城市的数目和道路的数目。
第二行 n 个正整数,每两个整数之间用一个空格隔开,按标号顺序分别表示这 n n 个城市的商品价格。
接下来 m 行,每行有 3 3 个正整数 x,y,z ,每两个整数之间用一个空格隔开。如果 z=1 z = 1 ,表示这条道路是城市 x x 到城市 y 之间的单向道路;如果 z=2 z = 2 ,表示这条道路为城市 x x 和城市 y 之间的双向道路。

输出

一 个整数,表示最多能赚取的旅费。如果没有进行贸易,则输出 0 0

输入样例

5 5 
4 3 5 6 1 
1 2 1 
1 4 1 
2 3 2 
3 5 1 
4 5 2

输出样例

5

数据范围

输入数据保证 1 号城市可以到达 n n 号城市。
对于 10%的数据, 1n6
对于 30%的数据, 1n100 1 ≤ n ≤ 100
对于 50%的数据,不存在一条旅游路线,可以从一个城市出发,再回到这个城市。
对于 100%的数据, 1n100000 1 ≤ n ≤ 100000 1m500000 1 ≤ m ≤ 500000 1x 1 ≤ x yn y ≤ n , 1≤z≤21≤z≤2 , 1 1 ≤ 各城市水晶球价格 100 ≤ 100


解题思路(共3种方法)

法1——分层图spfa

分层图是一个神奇的东西,建的好可以大大简化题目。
针对这道题,我们可以把图分三层:

  • 第一层表示买之前,第二层表示买之后卖之前,第三层表示卖之后;
  • 每一层内部边权设为 0 0
  • 对于一个点 i,设 i i 能到 j,则从 i i 连一条边权为 v[i] 的单向边到 j+n j + n (即 j j 在第二层图中对应的点),表示在 i 买入后走到 j j
  • 同理,从 i+n 连一条边权为 v[i] v [ i ] 的单向边到 j+n+n j + n + n (即 j j 在第三层图中对应的点),表示在 i 卖出后走到 j j
  • 最后将 n n+n+n n + n + n 都连向一个终点 T T

那么,从 1 出发到 T T ,要么一直在第一层中移动,表示没有进行买卖;要么从第一层经过第二层到达第三层,表示经过了一次买卖操作。可以发现,这样我们就把所有买卖情况考虑到了,因此,最后做一个简单的spfa找最长路即可。

法2——两次spfa

minPrice[i] 表示从 1 1 i 的路线中经过的最小价格, maxPrice[i] m a x P r i c e [ i ] 表示从 i i n 的路线中经过的最大价格,那么 ans=maxi=1n(maxPrice[i]minPrice[i]) a n s = max i = 1 n ( m a x P r i c e [ i ] − m i n P r i c e [ i ] )
可以先正向用 spfa 求出 minPrice m i n P r i c e ,再反向用 spfa 求出 maxPrice m a x P r i c e

法3——tarjan+topo

从50%的数据范围中得到启发,如果原图是一个DAG,那么我们可以用拓扑序得到答案:用拓扑序遍历该图,同时记录下历史最小价格,每到一个点用它的价格减去历史最小价格来更新答案,最后就能得到最大利润了。
但是原图可能存在环,怎么办?我们可以用 tarjan 缩点。因为在同一个强连通分量中可以随意走动,所以缩点后新点的最低价格就是强连通分量中的最低价格,新点的最高价格就是强连通分量中的最高价格,于是我们成功把图转成了一个DAG,就可以用上述方法解决问题了。


Code#1

分层图spfa

#include<cstdio>
#include<queue>

using namespace std;

const int N = 100005;
const int M = 500005;
const int INF = 1e9;
int n, m, v[N], x, y, z, T, S;

struct Edge{
    int nxt, to, dis;
}edge[M<<2];
int head[N*3], edgeNum;
void addEdge(int from, int to, int dis){
    edge[++edgeNum].nxt = head[from];
    edge[edgeNum].to = to;
    edge[edgeNum].dis = dis;
    head[from] = edgeNum;
}

int dis[N*3];
bool inq[N*3];
void spfa(){
    for(int i = 1; i <= n * 3; i++)
        dis[i] = -INF;
    queue<int> q;
    q.push(S);
    dis[S] = 0;
    inq[S] = 1;
    while(!q.empty()){
        int cur = q.front(); q.pop(); inq[cur] = 0;
        for(int i = head[cur]; i; i = edge[i].nxt){
            if(dis[edge[i].to] < dis[cur] + edge[i].dis){
                dis[edge[i].to] = dis[cur] + edge[i].dis;
                if(!inq[edge[i].to]){
                    q.push(edge[i].to);
                    inq[edge[i].to] = 1;
                }
            }
        }
    }
}

int main(){
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; i++)
        scanf("%d", &v[i]);
    for(int i = 1; i <= m; i++){
        scanf("%d%d%d", &x, &y, &z);
        addEdge(x, y, 0);
        addEdge(x+n, y+n, 0);
        addEdge(x+n+n, y+n+n, 0);
        addEdge(x, y+n, -v[x]);
        addEdge(x+n, y+n+n, v[x]);
        if(z == 2){
            addEdge(y, x, 0);
            addEdge(y+n, x+n, 0);
            addEdge(y+n+n, x+n+n, 0);
            addEdge(y, x+n, -v[y]);
            addEdge(y+n, x+n+n, v[y]);
        }
    }
    S = 1, T = n+n+n+1;
    addEdge(n, T, 0);
    addEdge(n+n+n, T, 0);
    spfa();
    printf("%d\n", dis[T]);
    return 0;
}

Code#2

两次spfa

#include<algorithm>
#include<cstring>
#include<cstdio>
#include<queue>

using namespace std;

const int MAXN = 100005;
const int MAXM = 500005;
int n, m, p[MAXN], u, v, q, ans;

struct Edge{
    int nxt, to;
}edge[MAXM<<1], fedge[MAXM<<1];

int head[MAXN], edge_num;
int fhead[MAXN], fedge_num;
void add_edge(int from, int to){
    edge[++edge_num].nxt = head[from];
    edge[edge_num].to = to;
    head[from] = edge_num;
    fedge[++fedge_num].nxt = fhead[to];
    fedge[fedge_num].to = from;
    fhead[to] = fedge_num;
}

int max_price[MAXN], min_price[MAXN];
bool inq[MAXN];
void spfa1(){
    memset(inq, 0, sizeof inq);
    memset(min_price, 0x7f, sizeof min_price);
    queue<int> q;
    q.push(1);
    inq[1] = 1;
    min_price[1] = p[1];
    while(!q.empty()){
        int cur = q.front(); q.pop(); inq[cur] = 0;
        for(int i = head[cur]; i; i = edge[i].nxt){
            if(min_price[edge[i].to] > min(min_price[cur], p[edge[i].to])){
                min_price[edge[i].to] = min(min_price[cur], p[edge[i].to]);
                if(!inq[edge[i].to]){
                    q.push(edge[i].to);
                    inq[edge[i].to] = 1;
                }
            }
        }
    }
}

void spfa2(){
    memset(inq, 0, sizeof inq);
    memset(max_price, 0, sizeof max_price);
    queue<int> q;
    q.push(n);
    inq[n] = 1;
    max_price[n] = p[n];
    while(!q.empty()){
        int cur = q.front(); q.pop(); inq[cur] = 0;
        for(int i = fhead[cur]; i; i = fedge[i].nxt){
            if(max_price[fedge[i].to] < max(max_price[cur], p[fedge[i].to])){
                max_price[fedge[i].to] = max(max_price[cur], p[fedge[i].to]);
                if(!inq[fedge[i].to]){
                    q.push(fedge[i].to);
                    inq[fedge[i].to] = 1;
                }
            }
        }
    }
}

int main(){
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; i++) scanf("%d", &p[i]);
    for(int i = 1; i <= m; i++){
        scanf("%d%d%d", &u, &v, &q);
        add_edge(u, v);
        if(q == 2)  add_edge(v, u);
    }
    spfa1();
    spfa2();
    for(int i = 1; i <= n; i++) ans = max(ans, max_price[i] - min_price[i]);
    printf("%d", ans);
    return 0;
} 

Code#3

tarjan+topo

#include<cstring>
#include<cstdio>
#include<stack>
#include<queue>

using namespace std;

const int MAXM = 500005;
const int MAXN = 100005;
int n, m, x, y, z, p[MAXN], minprice = 0x7fffffff, maxp[MAXN], minp[MAXN];

inline int max(int a, int b){
    return a > b ? a : b;
}

inline int min(int a, int b){
    return a < b ? a : b;
}

struct Edge{
    int nxt, to;
}edge[MAXM<<1], e[MAXM<<1], fe[MAXM<<1];

int head[MAXN], edge_num;
int fh[MAXN], fe_n;
void add_edge(int from, int to){
    edge[++edge_num].nxt = head[from];
    edge[edge_num].to = to;
    head[from] = edge_num;
    fe[++fe_n].nxt = fh[to];
    fe[fe_n].to = from;
    fh[to] = fe_n;
}

bool vis[MAXN];
void dfs(int x){
    vis[x] = 1;
    for(int i = fh[x]; i; i = fe[i].nxt){
        if(!vis[fe[i].to])  dfs(fe[i].to);
    }
}

int h[MAXN], e_n, ind[MAXN];
void a_e(int from, int to){
    e[++e_n].nxt = h[from];
    e[e_n].to = to;
    h[from] = e_n;
    ind[to]++;
}

int low[MAXN], dfn[MAXN], belong[MAXN], scc, dex;
bool ins[MAXN];
stack<int> s;
void tarjan(int x){
    s.push(x);
    ins[x] = 1;
    low[x] = dfn[x] = ++dex;
    for(int i = head[x]; i; i = edge[i].nxt){
        if(!vis[edge[i].to])    continue;
        if(!dfn[edge[i].to]){
            tarjan(edge[i].to);
            low[x] = min(low[x], low[edge[i].to]);
        }
        else if(ins[edge[i].to])    low[x] = min(low[x], dfn[edge[i].to]);
    }
    if(low[x] == dfn[x]){
        scc++;
        while(1){
            int cur = s.top(); s.pop();
            ins[cur] = 0;
            belong[cur] = scc;
            maxp[scc] = max(maxp[scc], p[cur]);
            minp[scc] = min(minp[scc], p[cur]);
            if(cur == x)    break;
        }
    }
}

int f[MAXN], ans;
void topo(){
    queue<int> q;
    for(int s = 1; s <= scc; s++)
        if(!ind[s])
            q.push(s);
    while(!q.empty()){
        int cur = q.front(); q.pop();
        for(int i = h[cur]; i; i = e[i].nxt){
            ind[e[i].to]--;
            if(!ind[e[i].to]){
                q.push(e[i].to);
                minprice = min(minprice, minp[e[i].to]);
                ans = max(ans, maxp[e[i].to] - minprice);
            }
        }
    }
}

int main(){
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; i++) scanf("%d", &p[i]);
    for(int i = 1; i <= m; i++){
        scanf("%d%d%d", &x, &y, &z);
        add_edge(x, y);
        if(z == 2)  add_edge(y, x);
    }
    dfs(n);
    memset(maxp, 0, sizeof maxp);
    memset(minp, 0x7f, sizeof minp);
    for(int i = 1; i <= n; i++)
        if(!dfn[i] && vis[i])
            tarjan(i);
    for(int i = 1; i <= n; i++)
        for(int j = head[i]; j; j = edge[j].nxt)
            if(vis[edge[j].to] && belong[edge[j].to] != belong[i])
                a_e(belong[i], belong[edge[j].to]);
    topo();
    printf("%d\n", ans);
    return 0;
}
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值