2021睿抗决赛 猛犸不上 Ban
在一个名叫刀塔的国家里,有一只猛犸正在到处跑着,希望能够用它的长角抛物技能来撞飞别人。已知刀塔国有 N 座城市,城市之间由 M 条道路互相连接,为了拦住这头猛犸,每条道路上设置了 Vi 人的团队。
这只猛犸从 S 号城市出发,它可以选择:
- 在不重复地经过若干条道路后回到 S 号城市;
- 在不重复地经过若干条道路后到达 T 号城市。
猛犸经过一条道路后,就会把路上的人全部撞飞。作为一头爱喝雪碧的仁慈的猛犸,自然希望尽可能的少撞飞人。请你帮忙计算一下在最优的选择下,最少需要撞飞多少人才能够到达目标城市?
输入格式:
输入第一行是四个正整数 N,M,S,T (2≤N≤500,1≤M≤105),表示有 N 个城市,M 条道路,猛犸从 S 号城市出发,可以选择到达 T 号城市。
接下来的 M 行,每行三个正整数 Xi,Yi,Vi (0≤Vi≤100),表示从 Xi 号城市到 Yi 号城市有一条道路,道路上有 Vi 人的团队。道路可双向通行,城市编号从 1 开始,两个城市之间最多只有一条道路,且没有一条道路连接相同的城市。
数据保证两种选择里至少有一种是可行的。
输出格式:
输出两行,第一行是两个数字,分别对应上面的两种选择分别最少需要撞飞多少人。如果无论撞飞多少人都无法满足选择要求,则输出 -1
。
第二行是一个句子,如果第一种(回到原点)的选择比较好,就输出 Win!
,否则输出Lose!
。
输入样例:
5 6 1 5
1 2 1
2 3 2
3 4 3
4 1 5
3 5 4
4 5 1
输出样例:
在这里给出相应的输出。例如:
11 6
Lose!
思路:
通过题目不难理解,我们要根据题意求出2种情况下的最短路并输出,再比较2种情况,输出win和lose。
第一种情况说白了就是求 s结点自身到自身的最小自环。 第二种情况就是经典的 Dijkstra求 s 结点和 t 结点的最短路径。
先给出第二种情况的代码,这种情况的处理相对简单,直接调用模板即可求解,代码如下
到达 T 号城市的Dijkstra代码:
int dijkstra(){memset(vi,0,sizeof vi);memset(dist,0x3f,sizeof dist);priority_queue<pair<int,int>,vector<pair<int,int>>,greater<pair<int,int>>> q;dist[s]=0;q.push({0,s});while(!q.empty()){auto it=q.top();q.pop();int u=it.second;int w=it.first;if(vi[u]){continue;}vi[u]=1;for(auto it:a[u]){int v=it.second;int w2=it.first;if(dist[v]>w+w2){dist[v]=w+w2;q.push({dist[v],v});}}}return dist[t];
}
对于第一种情况:
判断自环第一眼反应是 dfs ,从 s结点的邻接结点出发一路递归搜索到 s结点,同时一路标记路径和访问过的结点防止有重复路径和重复结点,找出所有自环,选择最短自环。然而500个结点显然会超限。
解决方法就是再走一遍Dijkstra。但上面的Dijkstra模板中会将自身结点标记,标记后的结点不再访问,如果直接从 s结点出发,走完自环在回到 s结点就会出现最后无法回环的情况,因为最初 s结点就已被标记。此时就可以参考 dfs的做法,从 s结点的邻接点出发,求邻接点到 s结点的最短距离,此时的自环权值=s结点到邻接点之间的距离+邻接点到s的最短距离(不算邻接边)。为了保证邻接点一定走的是自环,就要将邻接路径标记,也就是 path[v][s] = 1;path[s][v] = 1,如果在做松弛的过程中碰到了标记过的结点就continue ; 此时对邻接点做Dijkstra,dist数组表示邻接点到其余结点的最短距离。然后遍历s结点所有的邻接点,对每个结点求Dijkstra,套入自环权值公式求出最短自环即可。
经过若干条道路后回到 S 号城市的 Dijkstra代码:
int dijkstra(int x) {memset(vi, 0, sizeof vi);memset(dist, 0x3f, sizeof dist);//此时的dist表示x结点到其余结点的最短路径 priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> q;dist[x] = 0;q.push({ 0,x });while (!q.empty()) {auto it = q.top();q.pop();int u = it.second;int w = it.first;if (vi[u]) {continue;}vi[u] = 1;for (auto it : a[u]) {int v = it.second;int w2 = it.first;if (path[u][v] || path[v][u]) {continue;}else {if (dist[v] > w + w2) {dist[v] = w + w2;q.push({ dist[v],v });}}}}return dist[s];
}
for (auto it : a[s]) {//自环下的最短路 int u = s;int v = it.second;int w = it.first;path[v][u] = 1;path[u][v] = 1;//默认s结点到其邻接结点的路径被访问 ans1 = min(ans1, w + dijkstra(v));path[v][u] = 0;path[u][v] = 0;
}
题目中所提到的 “不重复地经过” 会有一定的误导作用,但在Dijkstra算法中不存在求最短路但是重边的情况,而在本题中唯一的用途就是第一种情况下的 s->i,i->s
2次Dijkstra的本质相同,注意区别路径判断下的不同。
2次Dijkstra做完后根据题意输出就能AC了。
AC代码:
#include<iostream>
#include<algorithm>
#include<vector>
#include<queue>
#include<cstring>
using namespace std;
const int N = 5e2 + 10;
const int INF = 0x3f3f3f3f;
vector<pair<int, int>> a[N];
int n, m, s, t;
int path[N][N];
int ans1 = INF;
int ans2;
int dist[N];
bool vi[N];
int dijkstra() {memset(vi, 0, sizeof vi);memset(dist, 0x3f, sizeof dist);priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> q;dist[s] = 0;q.push({ 0,s });while (!q.empty()) {auto it = q.top();q.pop();int u = it.second;int w = it.first;if (vi[u]) {continue;}vi[u] = 1;for (auto it : a[u]) {int v = it.second;int w2 = it.first;if (dist[v] > w + w2) {dist[v] = w + w2;q.push({ dist[v],v });}}}return dist[t];
}int dijkstra(int x) {memset(vi, 0, sizeof vi);memset(dist, 0x3f, sizeof dist);//此时的dist表示x结点到其余结点的最短路径 priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> q;dist[x] = 0;q.push({ 0,x });while (!q.empty()) {auto it = q.top();q.pop();int u = it.second;int w = it.first;if (vi[u]) {continue;}vi[u] = 1;for (auto it : a[u]) {int v = it.second;int w2 = it.first;if (path[u][v] || path[v][u]) {continue;}else {if (dist[v] > w + w2) {dist[v] = w + w2;q.push({ dist[v],v });}}}}return dist[s];
}int main() {memset(path, 0, sizeof path);cin >> n >> m >> s >> t;for (int i = 1;i <= m;i++) {int u, v, w;cin >> u >> v >> w;a[u].push_back({ w,v });a[v].push_back({ w,u });}for (auto it : a[s]) {//自环下的最短路 int u = s;int v = it.second;int w = it.first;path[v][u] = 1;path[u][v] = 1;//默认s结点到其邻接结点的路径被访问 ans1 = min(ans1, w + dijkstra(v));path[v][u] = 0;path[u][v] = 0;}ans2 = dijkstra();//到 t城市的最短路 if (ans1 >= INF) {cout << "-1 ";}else {cout << ans1 << " ";}if (ans2 >= INF) {cout << "-1";}else {cout << ans2;}cout << endl;if (ans1 < ans2) {cout << "Win!" << endl;}else {cout << "Lose!" << endl;}return 0;
}