首页 > 其他分享 >AcWing341. 洛谷P1073, NOIP2009 最优贸易

AcWing341. 洛谷P1073, NOIP2009 最优贸易

时间:2022-12-23 12:44:15浏览次数:66  
标签:洛谷 int memset AcWing341 NOIP2009 st SPFA dmin dmax

AcWing题目传送门
洛谷题目传送门

题目大意

\(~~~~~~\)一个投机倒把的奸商想要通过城市不太健全的贸易系统坑点钱,任意城市都可以买入或者卖出水晶球,他想尽量在便宜的城市买入,在贵的城市卖出,以此赚取更高的差价,他必须从一号城市开始旅行,到\(n\)号城市结束。请问他最多可以赚多少钱?

解题思路

\(~~~~~~\)这题乍一看貌似是毫无头绪的,但是我们可以通过差价高推出,他应该价格尽量低 买入,价格尽量高的卖出,所以我们可以在从\(1\sim n\) 的路径中选择一个分界点,记作\(k\),这个\(k\)点的含义就是奸商决定在\(1\sim k\)的城市中买入,\(k\sim n\)中的城市卖出,基于我们的买入尽量低,卖出尽量高原则,我们可以得到我们的基本思路:

\(~~~~~~\)枚举所有的\(k\)点,找出\(1\sim k\)中需要的价格最小的点(有路径可以到达的),用\(dmin\)数组记录, 再找出\(k\sim n\)中需要的价格最大的点(存在到\(n\)的路径的),用\(dmax\)数组记录,最后统计答案时找出\(dmax_{i}-dmin_{i}\) 的值最大的点

具体实现

\(~~~~~~\)怎么找出\(1\sim k\)中的最小点呢,难道是枚举每条\(a_{i}\sim k(\forall a_{i}\in [1,k])\)的路中最便宜的点吗?这样未免也太慢了。我们可以采用\(SPFA\)算法,枚举从\(1\)开始的单源最短路(这里最短路的松弛操作需要略做改动),这样对于\(\forall k\in [1,n]\),所有的最短距离都计算好了。

对于\(k\sim n\)的最大点我们也采用类似以上的做法:
\(~~~~~~\)我们枚举从\(k\)开始的最短路,松弛操作同样改动,那我们就可以算出\(\forall k\in [1,n]\)到\(n\)点的最大价值了。但是这样需要做\(n\)遍\(SPFA\),一定是不能再时间上通过的,所以该算法应该经过一点改进。

\(~~~~~~\)经过观察可以发现所有的汇点都是\(n\),我们就可以自然地建一个反图(将所有的边方向反转),这样子反图上跑出来的以\(n\)为源点的最长路,就是原图上各点到\(n\)的最长路了。

对于代码

\(~~~~~~~~~~~~~~~~~~~~~~~~\)我们可以写两个\(SPFA\),一个求最短路,一个求最长路
\(~~~~~~~~~~~~~~~~~~~~~~~~\)也可以合并处理,这里两份代码都放上来了

代码

两个\(SPFA\)

#include <queue>
#include <cstring>
#include <iostream>

using namespace std;

const int N = 3e5 + 10, M = 2e6 + 10, INF = 0x3f3f3f3f;

int dmax[N], dmin[N], st[N];
int h[N], rh[N], e[M], ne[M], w[N], idx;
int n, m;

void add(int *h, int a, int b) 
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}

void spfa() 
{
    queue<int> q;
    memset(st, 0, sizeof st);
    memset(dmin, 0x3f, sizeof dmin);
    
    dmin[1] = w[1], st[1] = true, q.push(1);
    
    while (q.size()) 
    {
        auto t = q.front(); q.pop();
        st[t] = false ;
        
        for (int i = h[t]; ~i; i = ne[i]) 
        {
            int j = e[i];
            if (dmin[j] > min(dmin[t], w[j])) 
            {
                dmin[j] = min(dmin[t], w[j]);
                if (!st[j]) st[j] = true, q.push(j);
            }
        }
    }
}

void rspfa() 
{
    queue<int> q;
    memset(st, 0, sizeof st);
    memset(dmax, 0xcf, sizeof dmax);
    
    dmax[n] = w[n], st[n] = true, q.push(n);
    
    while (q.size()) 
    {
        auto t = q.front(); q.pop();
        st[t] = false ;
        
        for (int i = rh[t]; ~i; i = ne[i]) 
        {
            int j = e[i];
            if (dmax[j] < max(dmax[i], w[j])) 
            {
                dmax[j] = max(dmax[i], w[j]);
                if (!st[j]) q.push(j);
            }
        }
    }
}

int main() 
{
    scanf("%d%d", &n, &m);
    
    for (int i = 1; i <= n; i ++ ) 
        scanf("%d", &w[i]);
        
    memset(h, -1, sizeof h);
    memset(rh, -1, sizeof rh);
    
    while (m -- ) 
    {
        int u, v, t;
        scanf("%d%d%d", &u, &v, &t);
        add(h, u, v), add(rh, v, u);
        if (t == 2) add(h, v, u), add(rh, u, v);
    }
    
    spfa(), rspfa(); 
    
    int res = 0;
    for (int i = 1; i <= n; i ++ ) 
        res = max(res, dmax[i] - dmin[i]);
    printf("%d\n", res);
    
    return 0;
}

观察到我们写的两个\(SPFA\)其实有很多相似点,所以可以合并成一个,加上一些参数就行(用来区分, 例如:遍历的邻接表不同,起点不同,要求的最短和最长性质不同)
但是在合并的\(SPFA\)中要注意:\(memset\)的时候因为我们传进去的距离数组只是一个指针,所以与我们要的字节大小不符合,所以应该写成memset(d, 0x3f, sizeof dmin);当然这只是示例
\(合并的SPFA\)

#include <queue>
#include <cstring>
#include <iostream>

using namespace std;

const int N = 2e5 + 10, M = 1e6 + 10, INF = 0x3f3f3f3f;

int dmax[N], dmin[N], st[N];
int h[N], rh[N], e[M], ne[M], w[N], idx;
int n, m;

void add(int *h, int a, int b) 
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}

void spfa(int *h, int *d, int S, int type) 
{
    memset(st, 0, sizeof st);
    if (type) memset(d, 0xcf, sizeof dmax);
    else memset(d, 0x3f, sizeof dmin);
    
    queue<int> q;
    d[S] = w[S], q.push(S), st[S] = true;
    
    while (q.size()) 
    {
        auto t = q.front(); q.pop();
        st[t] = false ;
        
        for (int i = h[t]; ~i; i = ne[i]) 
        {
            int j = e[i];
            if (type) {
                if (d[j] < max(d[t], w[j])) 
                {
                    d[j] = max(d[t], w[j]);
                    if (!st[j]) q.push(j);
                }
            }
            else {
                if (d[j] > min(d[t], w[j])) 
                {
                    d[j] = min(d[t], w[j]);
                    if (!st[j]) q.push(j);
                }
            }
        }
    }
}

int main() 
{
    scanf("%d%d", &n, &m);
    
    for (int i = 1; i <= n; i ++ ) 
        scanf("%d", &w[i]);
        
    memset(h, -1, sizeof h);
    memset(rh, -1, sizeof rh);
    
    while (m -- ) 
    {
        int u, v, t;
        scanf("%d%d%d", &u, &v, &t);
        add(h, u, v), add(rh, v, u);
        if (t == 2) add(h, v, u), add(rh, u, v);
    }
    
    spfa(h, dmin, 1, 0); // 正着跑最短路
    spfa(rh, dmax, n, 1); // 反着跑最长路
    
    int res = 0;
    for (int i = 1; i <= n; i ++ ) 
        res = max(res, dmax[i] - dmin[i]);
    printf("%d\n", res);
    
    return 0;
}

写法对比

两个\(SPFA\):时间\(250ms\),码量\(94\)行,容易\(Debug\)(原因:分开逻辑清晰一点)
一个\(SPFA\):时间\(227ms\),码量\(80\)行,容易出\(Bug\),不好\(Debug\)(原因:我菜)

\(\color{Green}{Accepted!}\)

标签:洛谷,int,memset,AcWing341,NOIP2009,st,SPFA,dmin,dmax
From: https://www.cnblogs.com/StkOvflow/p/17000438.html

相关文章

  • 洛谷 P2679 [NOIP2015 提高组]子串
    \(70pts\):记\(sub_A(i)\)表示\(A\)的前\(i\)个字符构成的子串,相应地,\(sub_B(i)\)为\(B\)的前\(i\)个字符构成的子串。设\(f(i,j,k)\)表示在\(sub_A(i......
  • 洛谷P2680 运输计划(LCA + 二分 + 树上边差分)
    洛谷P2680运输计划​ 现在有一棵树,每条树边上都有正权值。接下来,有m个询问,每次询问给出两个结点,这两个结点之间有一条路径。现在你可以任选一条树边,将其边权置为0,请输......
  • 洛谷 P5401 [CTS 2019] 珍珠 题解
    题目链接令\(c_i\)表示第i种颜色的珍珠的数量,显然我们最多能装的瓶数是\(\sum\lfloor\frac{c_i}2\rfloor\)。也就是说,\(c_i\)为奇数的\(i\)的数量不能太多,这个数量要......
  • 洛谷 P6580 [Ynoi 2019] 美好的每一天~ 不连续的存在 题解
    既然是YNOI那肯定是要分块的。先考虑树是一条链的情况,可以直接回滚莫队,对n个节点组成的序列分块。在回滚莫队的过程中,当前维护的区间一共会扩展\(O(n\sqrtn)\)次,每次都是......
  • 洛谷 P1712 [NOI2016] 区间
    很多套路糅合在一起的一道题。记\(len_i=r_i-l_i\)。则所求转化为一个有\(m\)个区间的集合\(S\),满足:存在一个\(x\),使得对于所有\(S\)中的区间\(i\),有\(l_......
  • 洛谷P3224 [HNOI2012]永无乡 题解 splay tree 启发式合并
    题目链接:https://www.luogu.com.cn/problem/P3224主要知识点是:树上启发式合并,即每次合并将小的树里面的每个点合并大大的树里面,时间复杂度\(O(n\log^2n)\)。同时需要......
  • 洛谷-P1450 硬币购物
    P1450硬币购物容斥||\(dp\)+单调队列优化容易看出是个多重背包,然后拿单调队列优化一下后,计算量为\(O(4ns)\)这种做法的话就是单调队列优化板子题#include<bits/......
  • 洛谷 P1020 导弹拦截(递增子串 DP )
    题目大意:有一个数列,我们要获取一组子串,这个子串必须单调不增。问:(1)最长我们可以获取多长的这种子串(2)这个数列中最多有多少种这种子串解题思路:其中问题一是很典型的DP问题,最......
  • 洛谷 P1087 FBI树
    题目大意:已知一棵树由字符串组成,规定:若节点全1把这节点称为I节点。若节点全0把节点称为B节点。若节点含0同时含1把节点称为F节点。现在有一个字符串N长,左子树是前一半字......
  • 洛谷 P1088 火星人(乱搞)
    题目大意:已知有一串数字,问在原来的数字串的字典序加m后,应该输出多少解题思路:最简便的做法是用next_permutation,这个生成的全排列可以按照字典序递增,这里我用的是搜索的方法......