NOIP2009提高组 最优贸易 详解(分层图状态转移 + SPFA)

分层图状态转移 + SPFA

 

洛谷  P1073

链接:https://www.luogu.org/problemnew/show/1073

 

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

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

分析

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

  • 你可以在图上任意走动

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

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

n平方的算法不难得出:

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

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

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

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

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

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

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

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

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

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

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

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

  • 不买卖直接走向终点

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

  • 买卖一次后走向终点

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

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

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

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

到此,本题就解完了

附代码

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 #define oo 1<<18;
 4 const int maxn = 100010;
 5 struct u {
 6     int v,len; 
 7 };
 8 int n, m, v[maxn], d[maxn*3+1];
 9 vector<u> G[maxn*3+1];
10 
11 void addedge(int x,int y) {
12   G[x].push_back((u){y,0});
13   G[x+n].push_back((u){y+n,0});//第二层图我从n+1到2n进行编号 
14   G[x+2*n].push_back((u){y+2*n,0});//第三层图我从2*n+1到3*n进行编号 
15   G[x].push_back((u){y+n,-v[x]});
16   G[x+n].push_back((u){y+2*n,v[x]});
17   return;
18 }
19 
20 queue<int> Q;
21 bool inq[maxn*3+1];
22 
23 void spfa() {
24   for(int i = 1;i <= n;i++)    d[i] = -oo;
25   d[1] = 0;
26   inq[1] = true;
27   Q.push(1);
28   while(!Q.empty()) {
29       int tp = Q.front(); Q.pop();
30       inq[tp] = false;
31       int len = G[tp].size();
32       for(int i = 0;i < len;i++) {
33           u x = G[tp][i];
34           if(d[x.v] < d[tp] + x.len) {
35               d[x.v] = d[tp] + x.len;
36               if(inq[x.v] == false) {
37                   Q.push(x.v);
38           inq[x.v] = true;
39               }
40           } 
41       }
42   }
43 }
44 
45 int main() {
46 //    freopen("d.txt","r",stdin);  调试用的 
47     cin >> n >> m;
48     for(int i = 1;i <= n;i++) cin >> v[i];
49     for(int i = 1,x,y,z;i <= m;i++) {
50         cin >> x >> y >> z;
51         addedge(x,y);
52         if(z == 2) addedge(y,x);
53     }
54     G[n].push_back((u){3*n+1,0});
55     G[n*3].push_back((u){3*n+1,0});//超级终点是3*n+1编号 
56     n = 3*n + 1; //把n改成超级终点的编号,方便spfa操作 
57 
58     spfa();
59     cout << d[n] << endl;
60     return 0;
61 }

 

posted @ 2017-11-01 17:11  Frank的成长之路  阅读(340)  评论(0编辑  收藏  举报