P2502 [HAOI2006]旅行
P2502 [HAOI2006]旅行
题目
题目描述
Z 小镇是一个景色宜人的地方,吸引来自各地的观光客来此旅游观光。Z 小镇附近共有 \(n\) 个景点(编号为 \(1,2,3,\ldots,n\)),这些景点被 \(m\) 条道路连接着,所有道路都是双向的,两个景点之间可能有多条道路。
也许是为了保护该地的旅游资源,Z 小镇有个奇怪的规定,就是对于一条给定的公路 \(r_i\),任何在该公路上行驶的车辆速度必须为 \(v_i\)。
速度变化太快使得游客们很不舒服,因此从一个景点前往另一个景点的时候,大家都希望选择行使过程中最大速度和最小速度的比尽可能小的路线,也就是所谓最舒适的路线。
输入格式
第一行包含两个正整数 \(n,m\)。
接下来的 \(m\) 行每行包含三个正整数 \(x,y,v\)。表示景点 \(x\) 到景点 \(y\) 之间有一条双向公路,车辆必须以速度 \(v\) 在该公路上行驶。
最后一行包含两个正整数 \(s,t\),表示想知道从景点 \(s\) 到景点 \(t\) 最大最小速度比最小的路径。\(s\) 和 \(t\) 不可能相同。
输出格式
如果景点 \(s\) 到景点 \(t\) 没有路径,输出
IMPOSSIBLE
。否则输出一个数,表示最小的速度比。如果需要,输出一个既约分数。输入输出样例
输入 #1
4 2 1 2 1 3 4 2 1 4
输出 #1
IMPOSSIBLE
输入 #2
3 3 1 2 10 1 2 5 2 3 8 1 3
输出 #2
5/4
输入 #3
3 2 1 2 2 2 3 4 1 3
输出 #3
2
说明/提示
对于 \(100\%\) 的数据,\(1 \le x,y \le n \le 500\),\(1 \le v < 3 \times 10^4\),\(1 \le m \le 5 \times 10^3\),\(x \ne y\)。
思路
最短路-70pts~90pts
考虑\(minv_{i,j}\)表示从\(s\)出发到达点\(i\)的路径中,最大边权为\(j\)时,最小边权为\(minv\).显然,要让最大速度比最小速度最小,就要让\(minv\)尽量大,故初始化\(minv=0\),\(minv_{s,0}=inf\). 跑最短路即可.
这样相当于有\(v\cdot n\)个结点,有\(v\cdot n\cdot m\)条边(最坏情况下).但是不一定会跑满这样的复杂度,得分在\(80\)到\(90\)之间.
代码
SPFA
没吸氧TLE2个,吸氧TLE1个
#include <iostream>
#include <cstdio>
#include <queue>
#include <cstring>
using namespace std;
int read() {
int re = 0;
char c = getchar();
bool negt = false;
while(c < '0' || c > '9')
negt |= (c == '-') , c = getchar();
while(c >= '0' && c <= '9')
re = (re << 1) + (re << 3) + c - '0' , c = getchar();
return negt ? -re : re;
}
const int N = 510 , M = 5010 , V = 30010;
struct EDGE {
int to , nxt , v;
}ed[M * 2];
int head[N];
void addedge(int u , int v , int val) {
static int cnt = 0;
++cnt;
ed[cnt].to = v , ed[cnt].nxt = head[u] , ed[cnt].v = val , head[u] = cnt;
}
int n , m;
int minv[N][V];
bool inq[N][V];
int gcd(int a , int b) {
return b == 0 ? a : gcd(b , a % b);
}
int COUNT;
int main() {
n = read() , m = read();
for(int i = 1 ; i <= m ; i++) {
int u = read() , v = read() , val = read();
addedge(u , v , val);
addedge(v , u , val);
}
int s = read() , t = read();
memset(minv , 0 , sizeof(minv));
minv[s][0] = V;
queue <int> q;
q.push(s << 20) , inq[s][0] = true;
while(!q.empty()) {
if(COUNT > 100000000)
break;
int x = (q.front() >> 20) , v = (q.front() & (1 << 20) - 1);
q.pop() , inq[x][v] = false;
for(int i = head[x] ; i ; i = ed[i].nxt) {
++COUNT;
int y = ed[i].to;
int maxv = max(ed[i].v , v);
int minv_ = min(minv[x][v] , ed[i].v);
if(minv[y][maxv] < minv_) {
minv[y][maxv] = minv_;
q.push((y << 20) + maxv);
}
}
}
double ans = 1e6;
int k = -1;
for(int i = 1 ; i < V ; i++) {
if(minv[t][i] != 0x3f3f3f3f && 1.0 * i / (double)minv[t][i] < ans) {
k = i;
ans = 1.0 * i / (double)minv[t][i];
}
}
if(k == -1)
puts("IMPOSSIBLE");
else if(k % minv[t][k] == 0)
cout << k / minv[t][k];
else
printf("%d/%d" , k / gcd(k , minv[t][k]) , minv[t][k] / gcd(k , minv[t][k]));
return 0;
}
dijskra(part)
没吸氧TLE3个点,吸氧TLE1个
struct node {
int id , v , minv;
bool operator < (const node &b) const {
return minv < b.minv;
}
};
node make(int id , int v , int minv) {
node tmp;
tmp.id = id , tmp.v = v , tmp.minv = minv;
return tmp;
}
//in main
priority_queue <node> q;
q.push(make(s , 0 , minv[s][0]));
while(!q.empty()) {
int x = (q.top()).id , v = (q.top()).v;
q.pop();
if(vis[x][v])
continue;
vis[x][v] = true;
for(int i = head[x] ; i ; i = ed[i].nxt) {
int y = ed[i].to;
int maxv = max(ed[i].v , v);
int minv_ = min(minv[x][v] , ed[i].v);
if(minv[y][maxv] < minv_) {
minv[y][maxv] = minv_;
q.push(make(y , maxv , minv_));
}
}
}
Kruskal/并查集-100pts
只能说类似Kruskal,但不完全是.把边按照边权从小到大排序(从大到小也可),枚举每一条边,设它的权值为\(v\),则把所有边权不大于\(v\)的边按边权从大到小依次加入图中,当加了一条边后\(s\),\(t\)突然连通了,那刚刚加入的这条边的边权(主语) 就是(表判断) 从\(s\)到\(t\)的 边权最大值为\(v\)时的 最大的边权最小值(宾语).(有点绕)
时间复杂度为\(O(m^2)\).
代码
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define reg register
using namespace std;
const int N = 510 , M = 5010 , V = 3010;
int read() {
int re = 0;
char c = getchar();
while(c < '0' || c > '9')
c = getchar();
while(c >= '0' && c <= '9')
re = (re << 1) + (re << 3) + c - '0' , c = getchar();
return re;
}
struct EDGE {
int x , y , speed;
}ed[M];
bool cmp(EDGE a , EDGE b) {
return a.speed < b.speed;
}
int n , m;
namespace UF {//Union-Find
int fa[N];
void init() {
for(int i = 1 ; i <= n ; i++)
fa[i] = i;
}
int findroot(reg int x) {
return fa[x] == x ? x : (fa[x] = findroot(fa[x]));
}
void uni(reg int x , reg int y) {
if(findroot(x) != findroot(y))
fa[findroot(x)] = fa[y];
}
}
int gcd(int a , int b) {
return b == 0 ? a : gcd(b , a % b);
}
int main() {
n = read() , m = read();
for(int i = 1 ; i <= m ; i++)
ed[i].x = read() , ed[i].y = read() , ed[i].speed = read();
sort(ed + 1 , ed + m + 1 , cmp);
reg int fracup = V , fracdown = 1;
reg int s = read() , t = read();
for(reg int i = m ; i > 0 ; i--) {
if(ed[i].speed == ed[i + 1].speed)
continue;
UF::init();
for(reg int j = i ; j > 0 ; j--) {
UF::uni(ed[j].x , ed[j].y);
if(UF::findroot(s) == UF::findroot(t)) {
if(1.0 * ed[i].speed / ed[j].speed < 1.0 * fracup / fracdown)
fracup = ed[i].speed , fracdown = ed[j].speed;
break;
}
}
if(UF::findroot(s) != UF::findroot(t))
break;
}
if(fracup == V)
puts("IMPOSSIBLE");
else if(fracup % fracdown == 0)
cout << fracup / fracdown;
else
printf("%d/%d" , fracup / gcd(fracup , fracdown) , fracdown / gcd(fracup , fracdown));
return 0;
}