题目描述

C国有n个大城市和m条道路,每条道路连接这n个城市中的某两个城市。任意两个城市之间最多只有一条道路直接相连。这m条道路中有一部分为单向通行的道路,一部分为双向通行的道路,双向通行的道路在统计条数时也计为1条。

C国幅员辽阔,各地的资源分布情况各不相同,这就导致了同一种商品在不同城市的价格不一定相同。但是,同一种商品在同一个城市的买入价和卖出价始终是相同的。

商人阿龙来到C国旅游。当他得知同一种商品在不同城市的价格可能会不同这一信息之后,便决定在旅游的同时,利用商品在不同城市中的差价赚回一点旅费。设C国n个城市的标号从1-n,阿龙决定从1号城市出发,并最终在n号城市结束自己的旅行。在旅游的过程中,任何城市可以重复经过多次,但不要求经过所有n个城市。阿龙通过这样的贸易方式赚取旅费:他会选择一个经过的城市迈入他最喜欢的商品——水晶球,并在之后经过的另一个城市卖出这个水晶球。用赚取的差价当作旅费。由于阿龙主要是来C国旅游,他决定这个贸易只进行最多一次。当然,在赚不到差价的情况下它就无需进行贸易。

假设C国有5个大城市,城市的编号和道路连接情况如下图,单向箭头表示这条道路为单向通行。双向箭头表示这条道路为双向通行。

假设1~n号城市的水晶球价格分别为4,3,5,6,1。

阿龙可以选择如下一条线路:1->2->3->5,并在2号城市以3的价格买入水晶球,在3号城市以5的价格卖出水晶球,赚取的旅费数为2。

阿龙也可以选择如下一条线路:1->4->5->4->5,并在第1次到达5号城市时以1的价格买入水晶球,在第2次到达4号城市时以6的价格卖出水晶球,赚取的旅费数为5。

现在给出n个城市的水晶球价格,m条道路的信息(每条道路所连接的两个城市的编号以及该条道路的通行情况)。请你告诉阿龙,他最多能赚钱多少旅费。

输入描述:

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

输出描述:

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

示例1

输入
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号城市。
对于10%的数据,1≤n≤6。
对于30%的数据,1≤n≤100。
对于50%的数据,不存在一条旅游路线,可以从一个城市出发,再回到这个城市。
对于100%的数据,1≤n≤100000,1≤m≤500000,1≤x,y≤n,1≤z≤2,1≤各城市水晶球价格≤100。

解答

分层图状态转移 + SPFA

其实此题可以不用强连通分量缩点,还有更优美的解法,只需60行代码

主要思想是类似“分层图”,或者类似“DAG”(有向无环图)的状态转移思想,特别是针对这种状态量相互影响的问题,分层图思想很实用。

分析

读完这道题,可以发现这样的事实:

  • 你可以在图上任意走动

  • 最终答案只与你的买入与卖出价格有关(我们就把买入卖出价值作为边权)

  • 如果你买入了一个水晶球,你是没有不卖它的道理的(显然咯,买了不***亏...)

n平方的算法不难得出:

我只关心我在哪里买了这个水晶球,在哪里把它卖出去,并且,我能否从起点走到我的买入点,从买入点走到卖出点,然后在走到n

因此,先枚举两个点再bfs检查能否到达,然后更新答案。

而此题的难点在与你如何知道你是否能够到达买入,卖出,钟点(即上两行 并且 后面我说的话),和你能否把所有可能的情况考虑在内。

分层图可以很好的解决这个问题。

由于可以任意走动,所以我们可以建一张图,令图上的边全都是0,表示我的走动对我最终的结果没有影响。

考虑某个点 i ,它买入或者卖出水晶球的花费是v[i] 。

那么 当我们进行买入操作,我们就建立一条有向边转移到一张新图上,边的大小为-v[i],指向点i所能到达的点(在第二层图上)而这张新图就是我们的第二层图。

它表示:假如我选择走了这条边,就是我在这个点买了这个水晶球,我不会反悔,并且我接下来考虑在某个点卖它。

当我们进行卖出操作,我们建立一条有向边转移到第三层图上,边的大小为v[i],指向i所能到达的点(在第三层图上)。

它表示:假如我选择走了这条边,就是我在这个点卖了这个水晶球,我不会反悔,并且我接下来考虑走向终点。

可以发现,从第一层图走到第二层图走到第三层图走到终点,这就是一个合法的选择,而且分层图把所有可能的决策都考虑到了。

最后走向终点,我们有两种合法的操作:

  • 不买卖直接走向终点

直接在第一层图的n号节点建立边权为0的有向边接入一个“超级终点”

  • 买卖一次后走向终点

在第三层图的n号节点建立边权为0的有向边接入“超级终点”

最后解释一下为什么我们要分层:

因为当你分了层,你就可以从还未买入这个状态,转移到已经买入准备卖出这个状态,然后在转移到从卖出点走向终点的状态。由于有向边的建立,你不能从第二/三层走回第一层图,这保证了你只做一次买卖,而不是无限做买卖,符合了题目的要求

而我们最终的答案,就是求从第一层图的1号点,经过三层图走到“超级终点”的最长路,如图所示。

到此,这道题就解完了。

代码如下

#include<bits/stdc++.h>
using namespace std;
#define oo 1<<18;
const int maxn = 100010;
struct u {
    int v,len; 
};
int n, m, v[maxn], d[maxn*3+1];
vector<u> G[maxn*3+1];

void addedge(int x,int y) {
  G[x].push_back((u){y,0});
  G[x+n].push_back((u){y+n,0});//第二层图我从n+1到2n进行编号 
  G[x+2*n].push_back((u){y+2*n,0});//第三层图我从2*n+1到3*n进行编号 
  G[x].push_back((u){y+n,-v[x]});
  G[x+n].push_back((u){y+2*n,v[x]});
  return;
}

queue<int> Q;
bool inq[maxn*3+1];

void spfa() {
  for(int i = 1;i <= n;i++)    d[i] = -oo;
  d[1] = 0;
  inq[1] = true;
  Q.push(1);
  while(!Q.empty()) {
      int tp = Q.front(); Q.pop();
      inq[tp] = false;
      int len = G[tp].size();
      for(int i = 0;i < len;i++) {
          u x = G[tp][i];
          if(d[x.v] < d[tp] + x.len) {
              d[x.v] = d[tp] + x.len;
              if(inq[x.v] == false) {
                  Q.push(x.v);
          inq[x.v] = true;
              }
          } 
      }
  }
}

int main() {
//    freopen("d.txt","r",stdin);  调试用的 
    cin >> n >> m;
    for(int i = 1;i <= n;i++) cin >> v[i];
    for(int i = 1,x,y,z;i <= m;i++) {
        cin >> x >> y >> z;
        addedge(x,y);
        if(z == 2) addedge(y,x);
    }
    G[n].push_back((u){3*n+1,0});
    G[n*3].push_back((u){3*n+1,0});//超级终点是3*n+1编号 
    n = 3*n + 1; //把n改成超级终点的编号,方便spfa操作 

    spfa();
    cout << d[n] << endl;
    return 0;
}


来源:fy1234567ok