[CQOI2005] 新年好
重庆城里有 n n n 个车站, m m m 条双向公路连接其中的某些车站。每两个车站最多用一条公路连接,从任何一个车站出发都可以经过一条或者多条公路到达其他车站,但不同的路径需要花费的时间可能不同。在一条路径上花费的时间等于路径上所有公路需要的时间之和。
佳佳的家在车站 1 1 1,他有五个亲戚,分别住在车站 a , b , c , d , e a,b,c,d,e a,b,c,d,e。过年了,他需要从自己的家出发,拜访每个亲戚(顺序任意),给他们送去节日的祝福。怎样走,才需要最少的时间?
输入格式
第一行: n , m n,m n,m,分别为车站数目和公路的数目。
第二行: a , b , c , d , e a,b,c,d,e a,b,c,d,e,分别为五个亲戚所在车站编号。
以下 m m m 行,每行三个整数 x , y , t x,y,t x,y,t,为公路连接的两个车站编号和时间。
输出格式
仅一行,包含一个整数 T T T,为最少的总时间。保证 T ≤ 1 0 9 T\le 10^9 T≤109。
样例输入 #1
6 6
2 3 4 5 6
1 2 8
2 3 3
3 4 4
4 5 5
5 6 2
1 6 7
样例输出 #1
21
对于 40 % 40\% 40% 的数据,有 1 ≤ n ≤ 500 1≤n≤500 1≤n≤500, 1 ≤ m ≤ 2000 1≤m≤2000 1≤m≤2000。
对于 100 % 100\% 100% 的数据,有 1 ≤ n ≤ 50000 1≤n≤50000 1≤n≤50000, 1 ≤ m ≤ 100000 1≤m≤100000 1≤m≤100000, 1 ≤ a , b , c , d , e ≤ n 1\le a,b,c,d,e≤n 1≤a,b,c,d,e≤n, 1 ≤ x , y ≤ n 1≤x,y≤n 1≤x,y≤n, 1 ≤ t ≤ 10000 1≤t≤10000 1≤t≤10000。
本题要求的不是单源点最短路,而是从起点开始,走过 a , b , c , d , e a,b,c,d,e a,b,c,d,e五个点的最短距离。
考虑枚举遍历从1开始走遍 a , b , c , d , e a,b,c,d,e a,b,c,d,e点的顺序,方案数为 5 ! 5! 5!,每一个方案都要走过五条路径,例如方案 1 − b − a − e − d − c 1-b-a-e-d-c 1−b−a−e−d−c要走过路径 ( 1 , b ) , ( b , a ) , ( a , e ) , ( e , d ) , ( d , c ) (1,b),(b,a),(a,e),(e,d),(d,c) (1,b),(b,a),(a,e),(e,d),(d,c)。考虑对于每一个方案,都做六遍dijkstra,每次求出来从 1 , a , b , c , d , e 1,a,b,c,d,e 1,a,b,c,d,e中一个点到其他点的最短距离,对于该方案所求的结果就是5个点对之间最短路径的和,这样时间复杂度就是 O ( 6 ! m l o g m ) O(6!mlogm) O(6!mlogm)。考虑先求出从这六个点出发到其他点的最短路径,在枚举所有方案,这样就起到了一个预处理的左右,时间复杂度为 O ( 6 m l o g m + 5 ! ) O(6mlogm+5!) O(6mlogm+5!)。
#include <iostream>
#include <cstring>
#include <queue>
using namespace std;
typedef pair<int,int> PII;
const int N = 50010, M = 1e5 * 2 + 10;
int dist[10][N];
bool st[N];
int h[N], e[M], ne[M], w[M];
int tot;
int source[10];
int n, m;
void add(int a, int b, int c)
{
e[tot] = b, ne[tot] = h[a], w[tot] = c, h[a] = tot ++ ;
}
void dijkstra(int start, int dist[])
{
memset(dist, 0x3f, N * 4);
memset(st, 0, sizeof st);
dist[start] = 0;
priority_queue<PII, vector<PII>, greater<PII>> heap;
heap.push({dist[start], start});
while (heap.size())
{
auto t = heap.top();
heap.pop();
int ver = t.second, distance = t.first;
if (st[ver]) continue;
st[ver] = 1;
for (int i = h[ver]; ~i; i = ne[i])
{
int j = e[i];
if (dist[j] > dist[ver] + w[i])
{
dist[j] = dist[ver] + w[i];
heap.push({dist[j], j});
}
}
}
return ;
}
int dfs(int u, int last, int val)
{
if (u == 6) return val;
int res = 0x3f3f3f3f;
for (int i = 2; i <= 6; i ++ )
{
int j = source[i];
if (st[i]) continue;
st[i] = 1;
res = min(res, dfs(u + 1, i, val + dist[last][j]));
st[i] = 0;
}
return res;
}
int main()
{
cin >> n >> m;
for (int i = 2; i <= 6; i ++ )
cin >> source[i];
source[1] = 1;
memset(h, -1, sizeof h);
for (int i = 1; i <= m; i ++ )
{
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
add(a, b, c), add(b, a, c);
}
for (int i = 1; i <= 6; i ++ )
dijkstra(source[i], dist[i]);
memset(st, 0, sizeof st);
printf("%d\n", dfs(1, 1, 0));
return 0;
}
通信线路
在郊区有 N N N座通信基站, P P P条双向电缆,第 i i i条电缆连接基站 A i A_i Ai和 B i B_i Bi。
特别地, 1 1 1号基站是通信公司的总站, N N N号基站位于一座农场中。
现在,农场主希望对通信线路进行升级,其中升级第 i i i条电缆需要花费 L i L_i Li。
电话公司正在举行优惠活动。农产主可以指定一条从 1 1 1号基站到 N N N号基站的路径,并指定路径上不超过 K K K条电缆,由电话公司免费提供升级服务。
农场主只需要支付在该路径上剩余的电缆中,升级价格最贵的那条电缆的花费即可。
求至少用多少钱可以完成升级。
输入格式
第 1 1 1行:三个整数 N , P , K N,P,K N,P,K。
第 2.. P + 1 2..P+1 2..P+1行:第 i + 1 i+1 i+1 行包含三个整数 A i , B i , L i A_i,B_i,L_i Ai,Bi,Li。
输出格式
包含一个整数表示最少花费。
若 1 1 1号基站与 N N N号基站之间不存在路径,则输出 −1。
数据范围
0 ≤ K < N ≤ 1000 , 1 ≤ P ≤ 10000 , 1 ≤ L i ≤ 1000000 0≤K<N≤1000,1≤P≤10000,1≤Li≤1000000 0≤K<N≤1000,1≤P≤10000,1≤Li≤1000000
输入样例:
5 7 1
1 2 5
3 1 4
2 4 8
3 2 3
5 2 9
3 4 7
4 5 6
输出样例:
4
从1号节点到n号节点之间有若干条路径,本题是要在所有路径里找第 k + 1 k+1 k+1大电缆的最小值,因此可以考虑使用二分来做。
可以用二分的情况是所求的答案在某个分界点,具有某个性质,在这个分界点的一边满足这个性质,另一边不满足。
1.对于答案x,需要满足的性质就是从1走到n需要经过大于x的边数小于等于k,作为分界点,此时大于x的边数应该等于k。
2.对于答案右边的区间,x变大了,大于x的边数就会减小,即大于x的边数小于k,同样满足性质;
2.对于答案左边的区间,x变小了,大于x的边数就会增加,即大于x的边数大于k,不满足性质。
接下来需要判断答案x是否满足上面性质。我们把边权大于x的边权置为1,小于等于x的边权置为0,这样一来图就是一个只有0,1边权的图,我们可以求出一条从1到n的最短路径,如果路径长度 ≤ k \leq k ≤k,则说明x可行。
答案区间为 [ 0 , 1 e 6 + 1 ] [0,1e6+1] [0,1e6+1],取到0则说明存在长度 ≤ k \leq k ≤k的路径,取到 1 e 6 + 1 1e6+1 1e6+1则说明不存在通路。
#include <iostream>
#include <cstring>
#include <deque>
using namespace std;
const int N = 1010, M = 200010;
int h[N], e[M], ne[M], w[M];
int tot;
int dist[N];
bool st[N];
int n, p, k;
void add (int a, int b, int c)
{
e[tot] = b, ne[tot] = h[a], w[tot] = c, h[a] = tot ++ ;
}
bool check(int x)
{
memset(dist, 0x3f, sizeof dist);
memset(st, 0, sizeof st);
dist[1] = 0;
deque<int> q;
q.push_back(1);
while (q.size())
{
int t = q.front();
q.pop_front();
if (st[t]) continue;
st[t] = 1;
for (int i = h[t]; ~i; i = ne[i])
{
int j = e[i], val = w[i] > x;
if (dist[j] > dist[t] + val)
{
dist[j] = dist[t] + val;
if (!val) q.push_front(j);
else q.push_back(j);
}
}
}
return dist[n] <= k;
}
int main()
{
cin >> n >> p >> k;
memset(h, -1, sizeof h);
while (p -- )
{
int a, b, c;
cin >> a >> b >> c;
add (a, b, c), add(b, a, c);
}
int l = 0, r = 1e6 + 1;
while (l < r)
{
int mid = l + r >> 1;
if (check(mid)) r = mid;
else l = mid + 1;
}
if (r == 1e6 + 1) r = -1;
cout << r;
return 0;
}
道路与航线
农夫约翰正在一个新的销售区域对他的牛奶销售方案进行调查。
他想把牛奶送到 T T T个城镇,编号为 1 ∼ T 1∼T 1∼T。
这些城镇之间通过 R R R条道路 (编号为 1 1 1到 R R R) 和 P P P条航线 (编号为 1 1 1到 P P P) 连接。
每条道路 i或者航线 i连接城镇 A i A_i Ai到 B i B_i Bi,花费为 C i C_i Ci。
对于道路, 0 ≤ C i ≤ 10 , 000 0≤Ci≤10,000 0≤Ci≤10,000;然而航线的花费很神奇,花费 C i Ci Ci可能是负数 ( − 10 , 000 ≤ C i ≤ 10 , 000 ) 。 (−10,000≤Ci≤10,000)。 (−10,000≤Ci≤10,000)。
道路是双向的,可以从 A i Ai Ai到 B i Bi Bi,也可以从 B i Bi Bi到 A i Ai Ai,花费都是 C i Ci Ci。
然而航线与之不同,只可以从 A i Ai Ai到 B i Bi Bi。
事实上,由于最近恐怖主义太嚣张,为了社会和谐,出台了一些政策:保证如果有一条航线可以从 A i Ai Ai到 B i Bi Bi,那么保证不可能通过一些道路和航线从 B i Bi Bi回到 A i Ai Ai。
由于约翰的奶牛世界公认十分给力,他需要运送奶牛到每一个城镇。
他想找到从发送中心城镇 S把奶牛送到每个城镇的最便宜的方案。
输入格式
第一行包含四个整数 T , R , P , S T,R,P,S T,R,P,S。
接下来 R R R行,每行包含三个整数(表示一个道路) A i , B i , C i Ai,Bi,Ci Ai,Bi,Ci。
接下来 P P P行,每行包含三个整数(表示一条航线) A i , B i , C i Ai,Bi,Ci Ai,Bi,Ci。
输出格式
第 1.. T 1..T 1..T行:第 i行输出从 S S S到达城镇 i i i的最小花费,如果不存在,则输出 NO PATH。
数据范围
1 ≤ T ≤ 25000 , 1 ≤ R , P ≤ 50000 , 1 ≤ A i , B i , S ≤ T 1≤T≤25000,1≤R,P≤50000,1≤Ai,Bi,S≤T 1≤T≤25000,1≤R,P≤50000,1≤Ai,Bi,S≤T
输入样例:
6 3 3 4
1 2 5
3 4 5
5 6 10
3 5 -100
4 6 -100
1 3 -10
输出样例:
NO PATH
NO PATH
5
0
-95
-100
本题中存在两种路线:
一种是道路,双向,边权非负。
一种是航线,单向,边权可正可负。同时保证保证如果有一条航线可以从 A i A_i Ai到 B i B_i Bi,那么保证不可能通过一些道路和航线从 B i B_i Bi回到 A i A_i Ai。
因为含有负边权,不能使用dijkstra,同时一般的spfa又会被卡,考虑把本题抽象为以下模型:
每一个大圆为一个连通块,大圆内的小圆(城镇)之间可以通过道路相互到达,其中深色小圆为起点,一个大圆可以通过单向的航线到达另一个大圆,大圆之间存在拓扑序。
#include <iostream>
#include <cstring>
#include <queue>
#include <vector>
using namespace std;
typedef pair<int,int> PII;
const int N = 25010, M = 1500010;
int id[N];
int h[N], e[M], ne[M], w[M], tot;
vector<int> block[N];
queue<int> q;
int dist[N];
bool st[N];
int pid;
int din[N];
int n, r, p, s;
void add(int a, int b, int c)
{
e[tot] = b, ne[tot] = h[a], w[tot] = c, h[a] = tot ++ ;
}
void dfs(int u, int pid)
{
id[u] = pid;
block[pid].push_back(u);
for (int i = h[u]; ~i; i = ne[i])
{
int j = e[i];
if (id[j]) continue;
dfs(j, pid);
}
return ;
}
void dijkstra(int x)
{
priority_queue<PII, vector<PII>, greater<PII>> heap;
for (auto i : block[x])
heap.push({dist[i], i});
while (heap.size())
{
auto t = heap.top();
int ver = t.second, distance = t.first;
heap.pop();
if (st[ver]) continue;
st[ver] = 1;
for (int i = h[ver]; ~i; i = ne[i])
{
int j = e[i];
if (dist[j] > dist[ver] + w[i])
{
dist[j] = dist[ver] + w[i];
if (id[j] == id[ver]) heap.push({dist[j], j});
}
if (id[j] != id[ver] && (-- din[id[j]]) == 0) q.push(id[j]);
}
}
return ;
}
void topsort()
{
memset(dist, 0x3f, sizeof dist);
dist[s] = 0;
for (int i = 1; i <= pid; i ++ )
if (din[i] == 0) q.push(i);
while (q.size())
{
int t = q.front();
dijkstra(t);
q.pop();
}
return ;
}
int main()
{
cin >> n >> r >> p >> s;
memset(h, -1, sizeof h);
while (r -- )
{
int a, b, c;
cin >> a >> b >> c;
add(a, b, c), add(b, a, c);
}
for (int i = 1; i <= n; i ++ )
{
if (id[i]) continue;
dfs(i, ++ pid);
}
while (p -- )
{
int a, b, c;
cin >> a >> b >> c;
add(a, b, c);
din[id[b]] ++ ;
}
topsort();
for (int i = 1; i <= n; i ++ )
{
if (dist[i] > 0x3f3f3f3f / 2) puts("NO PATH");
else cout << dist[i] << endl;
}
return 0;
}
[NOIP 2009 提高组] 最优贸易
C C C 国有 n n n 个大城市和 m m m 条道路,每条道路连接这 n n n 个城市中的某两个城市。任意两个城市之间最多只有一条道路直接相连。这 m m m 条道路中有一部分为单向通行的道路,一部分为双向通行的道路,双向通行的道路在统计条数时也计为 1 1 1 条。
C C C 国幅员辽阔,各地的资源分布情况各不相同,这就导致了同一种商品在不同城市的价格不一定相同。但是,同一种商品在同一个城市的买入价和卖出价始终是相同的。
商人阿龙来到 C C C 国旅游。当他得知同一种商品在不同城市的价格可能会不同这一信息之后,便决定在旅游的同时,利用商品在不同城市中的差价赚回一点旅费。设 C C C 国 n n n 个城市的标号从 1 ∼ n 1\sim n 1∼n,阿龙决定从 1 1 1 号城市出发,并最终在 n n n 号城市结束自己的旅行。在旅游的过程中,任何城市可以重复经过多次,但不要求经过所有 n n n 个城市。阿龙通过这样的贸易方式赚取旅费:他会选择一个经过的城市买入他最喜欢的商品――水晶球,并在之后经过的另一个城市卖出这个水晶球,用赚取的差价当做旅费。由于阿龙主要是来 C C C 国旅游,他决定这个贸易只进行最多一次,当然,在赚不到差价的情况下他就无需进行贸易。
假设 C C C 国有 5 5 5 个大城市,城市的编号和道路连接情况如下图,单向箭头表示这条道路为单向通行,双向箭头表示这条道路为双向通行。
假设 1 ∼ n 1\sim n 1∼n 号城市的水晶球价格分别为 4 , 3 , 5 , 6 , 1 4,3,5,6,1 4,3,5,6,1。
阿龙可以选择如下一条线路: 1 → 2 → 3 → 5 1\to2\to3\to5 1→2→3→5,并在 2 2 2 号城市以 3 3 3 的价格买入水晶球,在 3 3 3 号城市以 5 5 5 的价格卖出水晶球,赚取的旅费数为 2 2 2。
阿龙也可以选择如下一条线路: 1 → 4 → 5 → 4 → 5 1\to4\to5\to4\to5 1→4→5→4→5,并在第 1 1 1 次到达 5 5 5 号城市时以 1 1 1 的价格买入水晶球,在第 2 2 2 次到达 4 4 4 号城市时以 6 6 6 的价格卖出水晶球,赚取的旅费数为 5 5 5。
现在给出 n n n 个城市的水晶球价格, m m m 条道路的信息(每条道路所连接的两个城市的编号以及该条道路的通行情况)。请你告诉阿龙,他最多能赚取多少旅费。
输入格式
第一行包含 2 2 2 个正整数 n n n 和 m m m,中间用一个空格隔开,分别表示城市的数目和道路的数目。
第二行 n n n 个正整数,每两个整数之间用一个空格隔开,按标号顺序分别表示这 n n n 个城市的商品价格。
接下来 m m m 行,每行有 3 3 3 个正整数 x , y , z x,y,z x,y,z,每两个整数之间用一个空格隔开。如果 z = 1 z=1 z=1,表示这条道路是城市 x x x 到城市 y y y 之间的单向道路;如果 z = 2 z=2 z=2,表示这条道路为城市 x x x 和城市 y y y 之间的双向道路。
输出格式
一个整数,表示最多能赚取的旅费。如果没有进行贸易,则输出 0 0 0。
样例输入 #1
5 5
4 3 5 6 1
1 2 1
1 4 1
2 3 2
3 5 1
4 5 2
样例输出 #1
5
【数据范围】
输入数据保证 1 1 1 号城市可以到达 n n n 号城市。
对于 10 % 10\% 10% 的数据, 1 ≤ n ≤ 6 1\leq n\leq 6 1≤n≤6。
对于 30 % 30\% 30% 的数据, 1 ≤ n ≤ 100 1\leq n\leq 100 1≤n≤100。
对于 50 % 50\% 50% 的数据,不存在一条旅游路线,可以从一个城市出发,再回到这个城市。
对于 100 % 100\% 100% 的数据, 1 ≤ n ≤ 100000 1\leq n\leq 100000 1≤n≤100000, 1 ≤ m ≤ 500000 1\leq m\leq 500000 1≤m≤500000, 1 ≤ x , y ≤ n 1\leq x,y\leq n 1≤x,y≤n, 1 ≤ z ≤ 2 1\leq z\leq 2 1≤z≤2,$1\leq $ 各城市的编号 ≤ n \leq n ≤n。
水晶球价格 ≤ 100 \leq 100 ≤100。
先求出:
从1走到 i的过程中,买入水晶球的最低价格 d m i n [ i ] dmin[i] dmin[i];
从 i走到n的过程中,卖出水晶球的最高价格 d m a x [ i ] dmax[i] dmax[i];
然后枚举每个城市作为买卖的中间城市,求出 dmax[i] - dmin[i] 的最大值即可。
求 d m i n [ i ] dmin[i] dmin[i] 和 d m a x [ i ] dmax[i] dmax[i] 时,由于不是拓扑图,状态的更新可能存在环,因此不能使用动态规划,只能使用求最短路的方式。
另外,我们考虑能否使用 dijkstra 算法,如果当前 dmin[i] 最小的点是 5,那么有可能存在边 5 − > 6 , 6 − > 7 , 7 − > 5 5-> 6, 6-> 7, 7-> 5 5−>6,6−>7,7−>5,假设当前 d m i n [ 5 ] = 10 dmin[5] = 10 dmin[5]=10,则有可能存在 6 的价格是11, 但 7 的价格是3,那么 d m i n [ 5 ] dmin[5] dmin[5] 的值就应该被更新成3,因此当前最小值也不一定是最终最小值,所以dijkstra算法并不适用,我们只能采用 spfa 算法。
#include <iostream>
#include <cstring>
using namespace std;
const int N = 100010, M = 2000010;
int h[N], rh[N], e[M], ne[M];
int val[N];
int tot;
int q[N];
int dist[N];
bool st[N];
int n, m;
int dmin[N], dmax[N];
void add(int h[], int a, int b)
{
e[tot] = b, ne[tot] = h[a], h[a] = tot ++ ;
}
void spfa(int d[], int start, int h[], bool flag)
{
memset(st, 0, sizeof st);
if (!flag) memset(d, 0x3f, sizeof dmin);
d[start] = val[start];
int hh = 0, tt = 1;
q[0] = start;
st[start] = 1;
while (hh != tt)
{
int t = q[hh ++ ];
if (hh == N) hh = 0;
st[t] = 0;
for (int i = h[t]; ~i; i = ne[i])
{
int j = e[i];
if ((!flag && d[j] > min(d[t], val[j])) || (flag && d[j] < max(d[t], val[j])))
{
if (!flag) d[j] = min(d[t], val[j]);
else d[j] = max(d[t], val[j]);
if (!st[j])
{
q[tt ++ ] = j;
if (tt == N) tt = 0;
st[j] = 1;
}
}
}
}
}
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; i ++ )
cin >> val[i];
memset(h, -1, sizeof h);
memset(rh, -1, sizeof rh);
while (m -- )
{
int a, b, c;
cin >> a >> b >> c;
add (h, a, b), add(rh, b, a);
if (c == 2) add(h, b, a), add(rh, a, b);
}
spfa(dmin, 1, h, 0);
spfa(dmax, n, rh, 1);
int res = 0;
for (int i = 1; i <= n; i ++ )
res = max(res, dmax[i] - dmin[i]);
cout << res;
return 0;
}