2020-02-26刷题

最短路径

A1018. Public Bike Management

思路:

  1. 首先,为了编写代码,并得知所有结点中,哪个结点的点权(自行车数目)不够或者冗余,我们采取的方案是:把每个点的点权都减去Cmax/2,从而可以用点权的正负来直接判断当前车站是需要补给还是需要带走额外的车辆
  2. 因为除了要输出最短路径以外,还要输出从PBMC携带出来的自行车数目从问题车站Sp带回的自行车数目
    因此,我们采取的方案是:对每个顶点增加两个属性
    (1)从PBMC到当前车站必须携带的自行车数目Need;
    (2)到达当前车站时手上多余的自行车数目Remain。
    也就是说,
    如果当前车站u的点权weight[u]为正,说明需要从该车站额外带走自行车,所以,新的Remain等于旧的Remain加上weight[u]。
    如果当前车站u的点权weight[u]为负,说明当前车站需要补给的自行车数量为abs(weight[u])。此时,如果Remain大于0,就拿Remain来补给,但如果Remain补给后还是不够,剩下的只能从PBMC携带了,这时要记得更新Need,让Need加上这个不够的差值。
            //--当前结点编号为id--
            int id = tempPath[i];

            //--如果点权大于0,说明需要带走一部分自行车--
            if (weight[id] > 0) 
            {
                remain += weight[id];
            }

            //--如果点权不超过0,需要补给--
            else
            {
                //--如果当前持有量remain足够补给--
                if (remain > abs(weight[id])) 
                {
                    //--那么就用当前持有量remain减去需要补给的量--
                    remain -= abs(weight[id]);
                }

                //--如果当前持有量remain不够补给--
                else
                {
                    //--不够的部分从PBMC携带--
                    need += abs(weight[id]) - remain;

                    //--当前持有的自行车全部用来补给,所以归0--
                    remain = 0;
                }
            }
  1. 这道题需要先使用Dijkstra算法求出所有的最短路径。再使用DFS算法从这些最短路径中选出need最小的,如果need相同,则选择remain最小的

注意点:

  1. 大家要注意一点:从起点PBMC出发到达问题站点Sp的过程中,就需要调整路径上所有站点的状态至Perfect。
  2. 这道题不能单单的使用Dijkstra算法来搞定,因为minNeed和minRemain在路径上的传递不满足最优子结构,因为它不是简单的相加过程
    事实上,只有当所有路径都确定后,才能去选择最小的need最小的remain

代码逻辑:

  1. 这次定义的新变量中,有3个vector变量。pre[]前驱和tempPath, path临时路径和最优路径。
  2. 先看main()函数,首先输入车站的最大容量Cmax,顶点数n,问题站点Sp,边数m。然后使用邻接矩阵初始化图G,图G的初值均为INF。接下来,在输入点权的时候减去最大容量的一半Cmax/2。最后,更新边权G[u][v],而且要手动保证G[v][u] = G[u][v]
  3. 这一步开始,就直接进入到了Dijkstra算法和DFS算法
    这里我们让起点s作为参数传入Dijkstra算法,这里起点的编号为0。
    紧接着,将问题站点Sp作为参数传入DFS算法。
  4. Dijkstra算法:数组d[]记录最短距离。
    第一步,初始化d[],初值均为INF。接下来,定义前驱结点pre[],遍历n个结点,使pre[i] = i;
    第二步,遍历n个结点,找到未访问结点中d[]最小的。特殊情况:没有小于INF的d[u],说明剩下的顶点和起点s不连通。注意:如果顶点u已经访问,要记得将u的访问状态更新为true。
    第三步,重新遍历n个顶点。这次,如果顶点v未被访问,而且顶点u和v之间可到达。我们就看以u为中介点,能否使最短距离d[v]更小,
    可以的话,便更新。注意:更新d[v]的同时,还要将前驱结点数组pre[]先清空,再把顶点u加到pre[v]中(本来初始化中,pre[v]=v,现在pre[v]=u,也就是说v的前驱顶点是u)。
    不可以的话,那么如果以u为中介点,最短距离和d[v]相同。那么不清空pre[v],而是直接将顶点u加入到pre[v]中,也就是,顶点v的前驱顶点有2个,一个是v,后面的是u。
    注意:这里需要注意的一点是,昨天做的题可以在更新d[v]的时候,可以同步更新最大点权之和w[]最短路径条数num[],是因为w[]和num[]在路径上的传递满足最优子结构。然而这道题,我们只是同步更新了顶点v的前驱顶点u。没有同步更新最少携带的数目minNeed最少带回的数目minRemain。因为这俩不满足最优子结构,求解并不是简单的相加过程。只有在所有路径确定后,才可以去选择最小的need最小的remain
  5. DFS算法:
    我们以问题站点Sp作为参数传入DFS(int v)中。
    如果Sp是起点(0号顶点)(不一定一开始就进入v=0这个if{}中,因为DFS()在不断递归调用,递归到PBMC起点了),则向临时路径tempPath中添加该顶点Sp(起点)。然后倒着枚举tempPath中的每个顶点i。看看这个站点i的点权是否大于0——
    如果点权大于0,则带走特定数量的自行车。
    如果点权不超过0,说明需要补给,如果当前持有量够(可能已经经过一个站点了,从那个站点中带出来了一些),就拿来补给。如果不够,就说明需要从PBMC中带。
    当枚举完该tempPath临时路径的所有站点后,我们的remain和need都得到了更新,如果新的remain和needminNeed和minRemain更优,则更新minNeed和minRemain,同时,将当前这个tempPath临时路径更新为最优路径path。最后,将tempPath中的尾元素(也就是起点)删除掉。返回return。
    总结一下:当DFS(s)中的顶点为起点时,说明已经倒着递归到了最后一步。
    假如,当前DFS(v)中的结点v不是起点,那么就将顶点v加入到tempPath中,然后顺着枚举顶点v的每个前驱顶点pre[v][i]。最后递归结束,不断地返回过程中,不断地删除tempPath临时路径的尾元素。(原因是tempPath可能会不断地新添顶点,如果不删除干净,可能会计算错误)
  6. 输出答案:当Dijkstra算法和DFS算法均进行完后,输出minNeed和minRemain。值得一提的是,在输出仿真最优路径的时候,需要倒着枚举最优路径path的每一个顶点(原因是DFS()在递归的时候,是从问题站点(i.e. 目的地)开始,在起点(i.e.PBMC起点))结束的。

完整代码:

//--s为起点--
void Dijkstra(int s) 
{
    fill(d, d + MAXV, INF);
    for (int i = 0; i <= n; i++) 
    {
        pre[i].push_back(i);
    }

    d[s] = 0;


    for (int i = 0; i <= n; i++)      // 这里写成(i<n)或(i<=n)结果没有区别
                                      // 也就是循环n次和循环n+1次没有区别
    {
        int u = -1, MIN = INF;

        //--找到未访问的顶点中d[]最小的--
        for (int j = 0; j <= n; j++) 
        {
            if (vis[j] == false && d[j] < MIN) 
            {
                u = j;
                MIN = d[j];
            }
        }

        //--找不到小于INF的d[u],说明剩下的顶点和起点s不连通--
        if (u == -1) return;

        //--标记u为已访问--
        vis[u] = true;


        for (int v = 0; v <= n; v++) 
        {
            //--如果v未访问,并且u能到达v--
            if (vis[v] == false && G[u][v] != INF) 
            {
                if (d[u] + G[u][v] < d[v]) 
                {
                    //--优化d[v]--
                    d[v] = d[u] + G[u][v];

                    pre[v].clear();
                    pre[v].push_back(u);
                }

                else if (d[u] + G[u][v] == d[v]) 
                {
                    pre[v].push_back(u);
                }
            }
        }
    }
}



void DFS(int v) 
{
    if (v == 0) 
    {
        //--递归边界,叶子结点--
        tempPath.push_back(v);

        //--路径上tempPath上需要携带的数目,需要带回的数目--
        int need = 0, remain = 0;

        //--此处必须倒着枚举--
        for (int i = tempPath.size() - 1; i >= 0; i--) 
        {
            //--当前结点编号为id--
            int id = tempPath[i];

            //--如果点权大于0,说明需要带走一部分自行车--
            if (weight[id] > 0) 
            {
                remain += weight[id];
            }

            //--如果点权不超过0,需要补给--
            else
            {
                //--如果当前持有量remain足够补给--
                if (remain > abs(weight[id])) 
                {
                    //--那么就用当前持有量remain减去需要补给的量--
                    remain -= abs(weight[id]);
                }

                //--如果当前持有量remain不够补给--
                else
                {
                    //--不够的部分从PBMC携带--
                    need += abs(weight[id]) - remain;

                    //--当前持有的自行车全部用来补给,所以归0--
                    remain = 0;
                }
            }
        }


        //--如果需要从PBMC携带的自行车数目可以更少--
        if (need < minNeed) 
        {
            //--那么就优化minNeed--
            minNeed = need;

            //--同时覆盖minRemain--
            minRemain = remain;

            //--还要覆盖最优路径path--
            path = tempPath;
        }

        //--如果携带数目相同,带回数目变少--
        else if (need == minNeed && remain < minRemain) 
        {
            //--那么就优化minRemain--
            minRemain = remain;

            //--还要覆盖最优路劲path--
            path = tempPath;
        }

        tempPath.pop_back();    // 为什么要删除尾元素?
        return;
    }


    tempPath.push_back(v);
    for (int i = 0; i < pre[v].size(); i++)
    {
        DFS(pre[v][i]);
    }

    tempPath.pop_back();        // 这里也要删除尾元素?  
}




int main() 
{
    scanf("%d%d%d%d", &Cmax, &n, &Sp, &m);

    int u, v;
    fill(G[0], G[0] + MAXV * MAXV, INF);

    for (int i = 1; i <= n; i++) 
    {
        scanf("%d", &weight[i]);

        //--点权减去容量的一半--
        weight[i] -= Cmax / 2;
    }

    for (int i = 0; i < m; i++) 
    {
        scanf("%d%d", &u, &v);
        scanf("%d", &G[u][v]);

        G[v][u] = G[u][v];
    }


    Dijkstra(0);
    DFS(Sp);

    //---输出需要从PBMC携带的最少bike数目---
    printf("%d ", minNeed);

    //---输出仿真最优路径---
    for (int i = path.size() - 1; i >= 0; i--)
    {
        printf("%d", path[i]);
        if (i > 0) printf("->");
    }

    //---输出需要从问题站Sp带回的最少bike数目---
    printf(" %d", minRemain);
    return 0;
}


A1072. Gas Station

思路:

  1. 首先解决的是顶点的编号问题。我们通过写一个getID()函数来搞定。
  2. 枚举每个加油站,使用Dijkstra算法来得到所有居民房距离该加油站的最短距离。因为本题中所有的无向边都是真实存在的,所以Dijkstra算法中,顶点编号的范围应该是1~(n+m)。
  3. 在我们通过Dijkstra算法得到某个加油站的数组d[maxv]后,还需要获取其中的最小元素(也就是加油站与居民房的所有最短距离中的最近距离)。也许大家会有疑惑,我们通过D算法求得的,是所有居民房距离该加油站的最短距离。还要求所有居民房与加油站的平均距离

注意点:

  1. D算法要重复多次,所以要在开头,也就是每次执行算法前都要重置vis数组为false, d[]数组为INF
  2. 因为我们会定义一个变量最大的最近距离,因为是求最大,所以其初值必须设为一个较小的数(比如-1)
    (怎么理解这个最大的最近距离呢?先考虑加油站G1,找到离G1最近的居民房的距离。然后在看G2 G3等等一系列加油站,看看G2 G3离最近的居民房的距离有多大,在加油站G这个维度上,选一个最大的。在枚举单个加油站Gi的时候,选择距离最小的。)
    那么就是说,之前d[]数组,求得就是枚举每一个加油站,得到d[MAXV]则为G1离每个最近居民房的距离。然后在这里面,找一个最小的。这个最小的,就是该加油站与居民房的最近距离
    也可以说,一开始是在寻找距离某个特定加油站G最近(小)的那个居民房是多少号,距离又是多少。然后再对比每个加油站G与其最近的据民房的最近距离,这次取最大的。

代码逻辑:

  1. 首先看main()函数,为了将加油站ID可以顺利输入,我们定义顶点编号的类型为char型数组,来表示字符串(用string应该也可以)。
  2. 还是main()函数中,枚举所有的加油站,范围从n+1到n+m。枚举过程中,先通过D算法求出d[]数组,也就是距离第n+i号加油站,每一个居民房与它的最短距离。然后,得到某个特定加油站的d[]数组后,再针对该加油站枚举所有的居民房(当然这个for循环是嵌套在上个for循环中的)。
    在这第二个枚举所有的据民房中,我们要求的是——
    针对于某个特定加油站G,离其最近的居民房的距离所有居民房与该加油站的平均距离
    跳出第二个枚举所有居民房的循环后,这时我们已经得到了针对于某个加油站所有居民房与它的平均距离,还有离它最近的呢个居民房
    接下来,看的是,该加油站与所有居民房的距离中,是否有哪个居民房离它的距离大于了DS,如果有,则直接跳过该加油站,进入下一个加油站(continue)。
    如果没有出现大于DS的,则更新最大的最近距离(ansDis),如果到了后面加油站多了起来,最大的最近距离一样,则更新最小的平均距离,谁的平均距离最小,则选择哪个加油站

注意:minDis是最近距离,ansDis是最大的最近距离。因为minDis是求最小值,所以minDis的初值是INF。求解代码是这样(注意是d[j]<minDis,是小于号,看看d[j]是否小于minDis,如果小于当前的minDis,就更新minDis):

            double minDis = INF, avg = 0;
            //--更新当前最大的最近距离--
            if (d[j] < minDis) minDis = d[j];

然而,求ansDis是最大值,所以ansDis初值为-1。if判断中,则是大于号(看看minDis是否大于ansDis,如果大于当前的ansDis,就更新ansDis)——

        double ansDis = -1, ansAvg = INF;
        int ansID = -1;
        //--更新最大的最近距离--
        if (minDis > ansDis)
        {
            ansID = i;
            ansDis = minDis;
            ansAvg = avg;
        }

完整代码如下:

//---Dijkstra算法求所有顶点到起点s的最短距离---
void Dijkstra(int s) 
{
    memset(vis, false, sizeof(vis));
    fill(d, d + MAXV, INF);
    d[s] = 0;

    for (int i = 0; i < n + m; i++)
    {
        int u = -1, MIN = INF;

        for (int j = 1; j <= n + m; j++) 
        {
            if (vis[j] == false && d[j] < MIN) 
            {
                u = j;
                MIN = d[j];
            }
        }


        if (u == -1) return;

        vis[u] = true;

        for (int v = 1; v <= n + m; v++)
        {
            if (vis[v] == false && G[u][v] != INF) 
            {
                if (d[u] + G[u][v] < d[v]) 
                {
                    d[v] = d[u] + G[u][v];
                }
            }
        }
    }
}


//---将str[]转换为数字,若str是数字,则返回本身---
//---否则,返回去掉G之后的数,并加上n---
int getID(char str[]) 
{
    int i = 0, len = strlen(str), ID = 0;

    while (i < len) 
    {
        //--只要不是G,就转换为数字--
        if (str[i] != 'G') 
        {
            ID = ID * 10 + (str[i] - '0');
        }

        i++;
    }

    //--首位如果是G,返回n+ID--
    if (str[0] == 'G') return n + ID;

    //--首位不是G,返回ID--
    else return ID;
}

int main() 
{
    scanf("%d%d%d%d", &n, &m, &k, &DS);

    //--u和v表示一条road的左右端点,w表示边权--
    int u, v, w;

    char city1[5], city2[5]; // 这里的字符串还是用char型数组来定义的

    fill(G[0], G[0] + MAXV * MAXV, INF);


    for (int i = 0; i < k; i++) 
    {
        //--以字符串的形式读入城市编号--
        scanf("%s %s %d", city1, city2, &w);

        u = getID(city1);
        v = getID(city2);

        //--边权--
        G[v][u] = G[u][v] = w;
    }

    //--ansDis存放最大的最近距离--
    //--ansAvg存放最小的平均距离--
    //--ansID存放最终的加油站ID--
    double ansDis = -1, ansAvg = INF;
    int ansID = -1;

    //--枚举所有的加油站--
    for (int i = n + 1; i <= n + m; i++) 
    {
        //---minDis为当前最大的最近距离,avg为平均距离---
        double minDis = INF, avg = 0;

        //---进行Dijkstra算法,求出d[]数组---
        Dijkstra(i);

        //---枚举所有据民房---
        //---求出当前的minDis和avg---
        for (int j = 1; j <= n; j++) 
        {
            //--存在距离大于DS的居民房,直接跳出--
            if (d[j] > DS)
            {
                minDis = -1;
                break;
            }

            //--更新当前最大的最近距离--
            if (d[j] < minDis) minDis = d[j];

            //--获取平均距离--
            avg += 1.0 * d[j] / n;
        }

        //--如果存在距离大于DS的居民房,则跳过该加油站--
        if (minDis == -1) continue;

        //--更新最大的最近距离--
        if (minDis > ansDis)
        {
            ansID = i;
            ansDis = minDis;
            ansAvg = avg;
        }

        //--更新最小平均距离--
        else if (minDis == ansDis && avg < ansAvg) 
        {
            ansID = i;
            ansAvg = avg;
        }
    }

    //--无解--

    if (ansID == -1) printf("No Solution\n");

    else
    {
        printf("G%d\n", ansID - n);
        printf("%.1f %.1f\n", ansDis, ansAvg);
    }

    return 0;
}

这次没有详细说D算法,因为D算法这次唯二的不同就是——

  1. 函数的头2步,分别是对vis[]和d[]数组的重新赋值(d[s]=0是都有的)
  2. 枚举范围是n+m
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,539评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,911评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,337评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,723评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,795评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,762评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,742评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,508评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,954评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,247评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,404评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,104评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,736评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,352评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,557评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,371评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,292评论 2 352

推荐阅读更多精彩内容

  • 102 二叉树的层次遍历 因为题目要求遍历的结果是一个二维列表,每一层节点的遍历结果是一个列表,所以每次出了入队树...
    nowherespyfly阅读 101评论 0 0
  • 1. 图的定义和基本术语 线性结构中,元素仅有线性关系,每个元素只有一个直接前驱和直接后继;树形结构中,数据元素(...
    yinxmm阅读 5,448评论 0 3
  • https://zh.visualgo.net/graphds 浅谈图形结构https://zh.visualgo...
    狼之独步阅读 4,146评论 0 0
  • 1)这本书为什么值得看: Python语言描述,如果学的Python用这本书学数据结构更合适 2016年出版,内容...
    孙怀阔阅读 12,464评论 0 15
  • 目录 1.流网络及最大流问题1.1 流网络1.2 最大流问题1.3 最大流问题的三种解法对比 2.Ford-Ful...
    王侦阅读 4,621评论 0 3