zoukankan      html  css  js  c++  java
  • 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 }
  • 相关阅读:
    struts2标签解释
    通过注册表修改XP默认登陆用户
    6类常见开机故障
    摩尔定律
    小红伞扫描病毒类型
    newInstance()方法和new关键字
    常见恶意软件种类及说明
    jquery1.9 中这些方法已经被删除(live、die、sub........)
    (转)Ubuntu下JDK7安装全过程并调试第一个带包的java程序
    js取消气泡事件、阻止浏览器的默认行为
  • 原文地址:https://www.cnblogs.com/frankscode/p/7767412.html
Copyright © 2011-2022 走看看