AcWing 383 观光

2023-11-26 22:20
文章标签 acwing 383 观光

本文主要是介绍AcWing 383 观光,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

题目描述:

“您的个人假期”旅行社组织了一次比荷卢经济联盟的巴士之旅。

比荷卢经济联盟有很多公交线路。

每天公共汽车都会从一座城市开往另一座城市。

沿途汽车可能会在一些城市(零或更多)停靠。

旅行社计划旅途从 S 城市出发,到 F 城市结束。

由于不同旅客的景点偏好不同,所以为了迎合更多旅客,旅行社将为客户提供多种不同线路。

游客可以选择的行进路线有所限制,要么满足所选路线总路程为 S 到 F 的最小路程,要么满足所选路线总路程仅比最小路程多一个单位长度。

3463_1.png

如上图所示,如果S = 1,F = 5,则这里有两条最短路线1->2->5,1->3->5,长度为6;有一条比最短路程多一个单位长度的路线1->3->4->5,长度为7。

现在给定比荷卢经济联盟的公交路线图以及两个城市 S 和 F,请你求出旅行社最多可以为旅客提供多少种不同的满足限制条件的线路。

输入格式

第一行包含整数 T,表示共有 T 组测试数据。

每组数据第一行包含两个整数 N 和 M,分别表示总城市数量和道路数量。

接下来 M 行,每行包含三个整数 A,B,L,表示有一条线路从城市 A 通往城市 B,长度为 L。

需注意,线路是 单向的,存在从A到B的线路不代表一定存在从B到A的线路,另外从城市A到城市B可能存在多个不同的线路。

接下来一行,包含两个整数 S 和 F,数据保证 S 和 F 不同,并且S、F之间至少存在一条线路。

输出格式

每组数据输出一个结果,每个结果占一行。

数据保证结果不超过10^9。

数据范围

2≤N≤1000,
1≤M≤10000,
1≤L≤1000,
1≤A,B,S,F≤N

输入样例:

2
5 8
1 2 3
1 3 2
1 4 5
2 3 1
2 5 3
3 4 2
3 5 4
4 5 3
1 5
5 6
2 3 1
3 2 1
3 1 10
4 5 2
5 2 7
5 2 7
4 1

输出样例:

3
2

分析:

本题虽然属于dijkstra算法的扩展应用,但是从不论是解题思路还是最终AC都没那么容易。题目的关键字有:正权边,存在重边,求最短路径条数与比最短路径长度多一的路径条数之和,换而言之,就是求出最短路径条数与次短路径条数之和,规定次短路径只能比最短路径长度多一。

先来说下我最初的思路,虽然只过了一半用例,但是其思想还是值得深究的。dijkstra在求最短路径的过程中核心思想是松弛操作,即d[j] = min(d[j],d[u] + w),这个式子充满了DP的思想,只不过不是按照正常的线性顺序去扩展状态的。现在对于一个节点而言,不仅有这个起点到节点的最短距离以及最短路径的条数这两个状态,还有次短距离以及次短路径的条数这两个状态,当状态比较多时,用状态机去求解就很方便了。f[i]和c1[i]表示到i点的最短路径长度和条数,g[i]和c2[i]表示到i点次短路径的长度和条数。当c2[i]不为0时,g[i] == f[i] + 1,c2[i]为0时表示没有次短路径。下面分析状态的转移,点j的最短路只能由点u的最短路转移而来,即d[j] = min(d[j],d[u] + w)这个式子还是不变的,c1[j]在f[j]被更新时同步被更新为c1[u],在遇见等于最短路径的情况时c1[j] += c1[u]。次短路径可能由u的次短路径转移而来,也可能由u的最短路径转移而来。如果u的最短路径加上边权恰好是j的最短路径,那么u的次短路径加上边权就是j的次短路径;如果u的最短路径加上边权恰好比j的最短路径大一,则u的最短路径加上边权就是j的次短路径,用式子表示就是f[u] + w < f[j]时,f[j] = f[u] + w,g[j] = g[u],c1[j] = c1[u],c2[j] = c2[u];f[u] + w == f[j]时,c1[j] += c1[u],c2[j] += c2[u];f[u] + w == f[j] + 1时,g[j] = g[u],c2[j] += c2[u];如果画个状态图上面状态转移的关系就一目了然了。我最初的思路就是dijkstra算法主体不变,只将最短路径长度加入堆中,然后在堆顶元素出堆时,加上上面的状态转移操作。逻辑似乎没有错误,但是为什么只过了一半用例呢?这是因为动态规划要求状态的无后效性,dijkstra算法正确性的前提就是取出堆中距离最小的顶点后,它的最短距离后面不会被更新为更小的了,这就可以理解为状态的无后效性,一个顶点出队时它的状态就不会再改变了。

如上图所示,dijkstra求最短路长度和条数的时候A最先出堆,然后是B和C出堆,然后C出堆,然后D出堆,我们假设C比B先出堆,C出堆后,A经过B是不可能更新C的最短路的,所以C的最短路径长度和最短路径的条数都不会被更新了。但是如果同时更新次短路的状态呢?A出堆后更新C和B的状态,然后C出堆,此时C的最短路径长度是1,最短路径路径条数是1,然后C去更新D的状态,D的次短路径此时是不存在的,次短路径的条数自然是0;然后B出堆,更新了C的次短路状态,但是C已经出堆了,不会再去更新D的次短路状态了,这时的算法就出错了。用一句话概括下就是我们固然可以通过一个顶点的状态去更新另一个顶点的状态,但是必须保证前一个顶点(出堆的时候)状态已经确定下来了,否则B更新C的状态后无法传递到D。

正确求解本题只需要将上述思想略加调整,既然dijkstra算法出堆时只有顶点的最短路信息被确定了,那么我们将次短路信息也加入到堆中,只有一个顶点次短路出堆时,它的次短路信息才会被确定下来。对应在算法上的改变就是B在更新C的状态时,不仅需要尝试把C的最短路径长度加入堆中(当然这里1 + 1 > 1没有松弛成功不用入堆),C的次短路径长度被更新为2也要加入到堆中,然后尽管C作为最短路时出堆了,作为次短路还可以再出堆一次,出堆时C的次短路信息就定下来了,也可以去更新D的次短路信息了。

整理下思路,我们需要放进优先级队列中顶点的信息有:路径长度、节点编号、路径类型,类型type为0时表示最短路径,type为1时表示次短路径,在执行状态转移时这里并不需要分类讨论出堆的是最短路径还是次短路径。设dist为路径长度加上边权之和,当dist小于最短路径长度时,尝试更新最短路径和次短路径,这时不用管type的原因在于只有最短路径才会满足这个分支条件;当dist等于最短路径长度时,更新下最短路径信息即可,这个分支被触发的前提也只可能是type是最短路径的情况下,如果u的次短路径出队加上边权等于j的最短路径的话,先于u的次短路径出堆的最短路径出堆时一定会更新掉j的最短路径;当dist等于最短路径长度+1时,如果j的次短路径条数为0表明这是第一次更新j的次短路,需要将次短路入队,同时更新次短路信息,这个分支type去0或者1的情况下都可能触发,对应了上面分析中次短路径可以由次短路径或者最短路径转移而来。具体的实现见代码:

#include <iostream>
#include <algorithm>
#include <queue>
#include <cstring>
using namespace std;
const int N = 1005,M = 10005;
int idx,h[N],e[M],w[M],ne[M];
int n,m,d[N][2],cnt[N][2];
bool st[N][2];
struct Node{int dist,idx,type;bool operator < (const Node X)const{return dist > X.dist;//把小于号定义为大于号,大根堆就变成小根堆了}
};
priority_queue<Node> pq;
void add(int a,int b,int c){e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}
int dijkstra(int s,int t){memset(d,0x3f,sizeof d);memset(cnt,0,sizeof cnt);memset(st,false,sizeof st);d[s][0] = 0,cnt[s][0] = 1;pq.push({d[s][0],s,0});while(pq.size()){Node node = pq.top();int u = node.idx,type = node.type;pq.pop();if(st[u][type])   continue;st[u][type] = true;for(int i = h[u];~i;i = ne[i]){int j = e[i];int dist = d[u][type] + w[i];if(dist < d[j][0]){if(dist + 1 == d[j][0]){d[j][1] = d[j][0],cnt[j][1] = cnt[j][0]; pq.push({d[j][1],j,1});}d[j][0] = dist,cnt[j][0] = cnt[u][0];pq.push({d[j][0],j,0});}else if(dist == d[j][0])    cnt[j][0] += cnt[u][0];else if(dist == d[j][0] + 1){if(!cnt[j][1]){d[j][1] = dist;pq.push({d[j][1],j,1});}cnt[j][1] += cnt[u][type];//这里一定要写成type,因为type是0或者1都可能触发该分支}   }}return cnt[t][0] + cnt[t][1];
}
int main(){int T,S,F,a,b,c;scanf("%d",&T);while(T--){memset(h,-1,sizeof h);idx = 0;scanf("%d%d",&n,&m);for(int i = 0;i < m;i++){scanf("%d%d%d",&a,&b,&c);add(a,b,c);}scanf("%d%d",&S,&F);printf("%d\n",dijkstra(S,F));}return 0;
}

 

这篇关于AcWing 383 观光的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/426390

相关文章

【AcWing】851. 求最短路

spfa算法其实是对贝尔曼福特算法做一个优化。 贝尔曼福特算法会遍历所有边来更新,但是每一次迭代的话我不一定每条边都会更新,SPFA是对这个做优化。 如果说dist[b]在当前这次迭代想变小的话,那么一定是dist[a]变小了,只有a变小了,a的后继(b)才会变小。 用宽搜来做优化,用一个队列,队列里边存的就是所有变小了的结点(队列里存的是待更新的点)。 基本思路就是我更新过谁,我再拿

【AcWing】852. spfa判断负环

#include<iostream>#include<algorithm>#include<cstring>#include<queue>using namespace std;const int N= 1e5+10;int n,m;int h[N],w[N],e[N],ne[N],idx;int dist[N],cnt[N];//cnt存最短路径的边数bool st[N];v

AcWing 282. 石子合并

必看的视频讲解↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ 【E28【模板】区间DP 石子合并——信息学竞赛算法】 合并过程总开销等于红色数字总和,可以理解为花费的总体力! f数组的含义是f【i】【j】是从第i堆石子开始到第j堆石子的花费体力最小值 如何理解三层for呢? 第一层for是控制区间长度len,第二层for是控制区间起点位置i,第三层for是控制区间

AcWing 897. 最长公共子序列

动态规划就是多见识应用题就完事儿了,也没有什么好说的。 讲解参考: 【E05 线性DP 最长公共子序列】 #include<iostream>#include<algorithm>#define N 1010using namespace std;char a[N],b[N];int n,m;int f[N][N];int main(){cin >> n >> m >> a

Golang | Leetcode Golang题解之第383题赎金信

题目: 题解: func canConstruct(ransomNote, magazine string) bool {if len(ransomNote) > len(magazine) {return false}cnt := [26]int{}for _, ch := range magazine {cnt[ch-'a']++}for _, ch := range ransomNo

C语言 | Leetcode C语言题解之第383题赎金信

题目: 题解: bool canConstruct(char * ransomNote, char * magazine){int r = strlen(ransomNote);//首先是我们的目标数组和我们的提供方数组长度int m = strlen(magazine);if (r > m)return false;//如果提供的数量都不够补充目标,那肯定是不行的。故:falseint

C++ | Leetcode C++题解之第383题赎金信

题目: 题解: class Solution {public:bool canConstruct(string ransomNote, string magazine) {if (ransomNote.size() > magazine.size()) {return false;}vector<int> cnt(26);for (auto & c : magazine) {cnt[c -

AcWing 2. 01背包问题

一定要看的视频讲解:↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ 【E08【模板】背包DP 01背包——信息学竞赛算法】 i表示放入i个物品,j表示第j个物品,用于访问体积v【j】 #include <iostream>#include <algorithm>using namespace std;const int N = 1010;int n, m;int v[N]

383. 赎金信【 力扣(LeetCode) 】

一、题目描述 给你两个字符串:ransomNote 和 magazine ,判断 ransomNote 能不能由 magazine 里面的字符构成。 如果可以,返回 true ;否则返回 false 。 magazine 中的每个字符只能在 ransomNote 中使用一次。 二、测试用例 示例 1: 输入:ransomNote = "a", magazine = "b"输出:fal

代码随想录算法训练营第六天|454.四数相加II;383. 赎金信;15. 三数之和;18. 四数之和

今日任务 ●  454.四数相加II ●  383. 赎金信 ●  15. 三数之和 ●  18. 四数之和 详细布置 454.四数相加II 建议:本题是 使用map 巧妙解决的问题,好好体会一下 哈希法 如何提高程序执行效率,降低时间复杂度,当然使用哈希法 会提高空间复杂度,但一般来说我们都是舍空间 换时间, 工业开发也是这样。 题目链接:. - 力扣(LeetCode) 题目链接/文