目录
新年好
通信线路
道路与航线
最优贸易
重庆城里有 n 个车站,m 条 双向 公路连接其中的某些车站。
每两个车站最多用一条公路连接,从任何一个车站出发都可以经过一条或者多条公路到达其他车站,但不同的路径需要花费的时间可能不同。
在一条路径上花费的时间等于路径上所有公路需要的时间之和。
佳佳的家在车站 1,他有五个亲戚,分别住在车站 a,b,c,d,e
过年了,他需要从自己的家出发,拜访每个亲戚(顺序任意),给他们送去节日的祝福。
怎样走,才需要最少的时间?
输入格式
第一行:包含两个整数 n,m 分别表示车站数目和公路数目。
第二行:包含五个整数 a,b,c,d,e 分别表示五个亲戚所在车站编号。
以下 m 行,每行三个整数 x,y,t,表示公路连接的两个车站编号和时间。
输出格式
输出仅一行,包含一个整数 T,表示最少的总时间。
数据范围
1≤n≤50000
1≤m≤10^5
1 1≤x,y≤n
1≤t≤100
输入样例:
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
输出样例:
21
思路:
先预处理 在爆搜
dist是一个二维数组,分别存0和其他5个点到其他点的最短路,你memset会清空之前跑的结果,但是4*N就只是把二位数组中的一个变成0x3f
#include
#include
#include
#include
using namespace std;
const int N=5e4+10,M=2e5+10,INF=1e9+10;
typedef pairPII;
int h[N],ne[M],e[M],w[M],idx;
int dist[6][N];
bool st[N];
int source[6];
int n,m;
//建表
void add(int a,int b,int c)
{
e[idx]=b;
w[idx]=c;
ne[idx]=h[a];
h[a]=idx++;
}
//遍历1 a b c d e 到其余各点的最短距离
//dijkstra堆优化
void dijkstra(int start,int pos)
{
memset(st,0,sizeof st);
memset(dist[pos],0x3f,sizeof dist[pos]);//这样只会初始化 dist[0][1~N],或者dist[1][1~N]...
dist[pos][start]=0;
priority_queue,greater>heap;
heap.push({0,start});
while(heap.size())
{
auto t=heap.top();
heap.pop();
int ver=t.second,distance=t.first;
if(st[ver]) continue;
st[ver]=true;
for(int i=h[ver];i!=-1;i=ne[i])
{
int j=e[i];
if(dist[pos][j]>dist[pos][ver]+w[i])
{
dist[pos][j]=dist[pos][ver]+w[i];
heap.push({dist[pos][j],j});
}
}
}
}
int res=INF;
//6个数全排列 u表示几个数 start表示当前是第几个数 distance 表示距离 6个数5个距离
void dfs(int u,int start,int distance)
{
if(u==6)
{
res=min(res,distance);
}
for(int i=1;i<=5;i++)
{
int next=source[i];
if(!st[i])
{
st[i]=true;
dfs(u+1,i,distance+dist[start][next]);
st[i]=false;
}
}
}
int main()
{
scanf("%d%d",&n,&m);
source[0]=1;
for(int i=1;i<=5;i++) scanf("%d",&source[i]);
memset(h,-1,sizeof h);
while(m--)
{
int a,b,c;scanf("%d%d%d",&a,&b,&c);
add(a,b,c);
add(b,a,c);
}
for(int i=0;i<=5;i++) dijkstra(source[i],i);
//在初始化一次 因为前面的堆优化都用了
memset(st,0,sizeof st);
dfs(1,0,0);
printf("%d\n",res);
return 0;
}
在郊区有 N 座通信基站,P 条 双向 电缆,第 i 条电缆连接基站 Ai 和 Bi。
特别地,1 号基站是通信公司的总站,N 号基站位于一座农场中。
现在,农场主希望对通信线路进行升级,其中升级第 i 条电缆需要花费 Li。
电话公司正在举行优惠活动。
农产主可以指定一条从 1 号基站到 N 号基站的路径,并指定路径上不超过 K 条电缆,由电话公司免费提供升级服务。
农场主只需要支付在该路径上剩余的电缆中,升级价格最贵的那条电缆的花费即可。
求至少用多少钱可以完成升级。
输入格式
第 1 行:三个整数 N,P,K。
第 2..P+1 行:第 i+1 行包含三个整数 Ai,Bi,Li。
输出格式
包含一个整数表示最少花费。
若 1 号基站与 N 号基站之间不存在路径,则输出 −1。
数据范围
0≤K
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 找一条代价最小的路径(最短路问题),每一条路径(1->N)的代价是这条路(组成这条路的某个节点到某个节点)的最大权值,但是可以有k条边免费,可以让k 条边权值为零
思路:
首先可以明确的是这道题目考察最短路算法(题目都说了求1->N中花费最少)
但是与以往直接求1->N 这条路径代价不同的是 它求的不是全部的边之和最小
而是求得是 每一条路径的最大边是这条路径的花费
并且在这条路径中可以让(1~K)条边不花费 可以设置一种特殊边 让(1~K)条边权值为0
所以可以设置一个数组dist[x][p] 表示 节点1到节点x 途中设置p条边权值为0
新加入的边是非0边.
那么我们面对每一条新加入的边(x,y,z)我们的d[y,p]=max(d[x,p],z)其中z为(x,y)权值.
新加入的边是0边.
如果新加入的边是权值为0的边,显然是d[y,p+1]=d[x,p].
max(dis[t][p],z)这个地方是表示新加入的这个边是需要掏钱的,所以就要和前面走过的边所用的费用进行对比,选取中最大的费用;dis[t][p-1]这个表示这个新加入的边免费升级,则新加入一条边需要支付的费用,还是前面的费用没有改变;min(dis[t][p-1],max(dis[t][p],z)),这里应该是指,新加入的这边,免费修和不免费修,两种方案的费用中取最小值;
#include
#include
#include
#include
using namespace std;
const int N=1010,M=2e5+10,INF=0x3f3f3f3f;
int h[N],ne[M],e[M],w[M],idx;
int dist[N][N];//dist[x][p]表示节点1到节点x 途中经历了p条权值为0的边的最小值
bool st[N];
int n,p,k;
int ans=INF;
//建表
void add(int a,int b,int c)
{
e[idx]=b;
w[idx]=c;
ne[idx]=h[a];
h[a]=idx++;
}
void spfa()
{
memset(dist,0x3f,sizeof dist);
memset(st,0,sizeof st);
dist[1][0]=0;//节点1到节点1的距离为0 并且此时没有用清零 没有设特殊边
queueq;
q.push(1);
st[1]=1;
//松弛原理
while(q.size())
{
int t=q.front();
q.pop();
st[t]=0;
for(int i=h[t];i!=-1;i=ne[i])
{
int j=e[i],z=w[i];
int x=max(dist[t][0],z);//最初都没有设特殊边 1-t->j 取最大值
if(dist[j][0]>x)//若是x小则更新
{
dist[j][0]=x;
if(!st[j])
{
q.push(j);
st[j]=1;
}
}
//1->...->j 设特殊边 1~k 找最小值
for(int p=1;p<=k;p++)
{
/*dist[t][p-1] 表示从 1->...->t->j 是不需要花费的免费的这需要设t->j
边权为0 故dist[t][p]=dist[t][p-1];
而 max(dist[t][p],z)说明 t->j这条边是需要花钱的(在1->...->t的途中已经经过了p条边权为0的边)
所以就要和前面走过的边所用的费用进行对比,选取中最大的费用
这里表示的意思是新加入的边 免费修和不免费修 两种方案取最小
*/
int y=min(dist[t][p-1],max(dist[t][p],z));
if(dist[j][p]>y)
{
dist[j][p]=y;
if(!st[j])
{
q.push(j);
st[j]=1;
}
}
}
}
}
}
int main()
{
scanf("%d%d%d",&n,&p,&k);
//建表初始化
memset(h,-1,sizeof h);
while(p--)
{
int a,b,c;scanf("%d%d%d",&a,&b,&c);
add(a,b,c);
add(b,a,c);
}
spfa();
//找达到1->n中最小值
for(int i=0;i<=k;i++) ans=min(ans,dist[n][i]);
if(ans==INF) cout<<"-1"<
可以用二分做 但是目前还没有彻底搞懂 ~ - ~
#include
#include
#include
using namespace std;
typedef pair PII;
const int N = 1010,M = 20010;
int n,m,k;
int h[N],ne[M],idx = 0;
struct edge {
int to,w;
}edges[M];
int dist[N];
bool st[N];
void add (int a,int b,int c) {
edges[idx] = {b,c};
ne[idx] = h[a];
h[a] = idx++;
}
bool check (int max_w) {
memset (dist,0x3f,sizeof (dist));
memset (st,false,sizeof (st));
dist[1] = 0;
priority_queue ,greater > heap;
heap.push ({0,1});
while (!heap.empty ()) {
int t = heap.top ().second;
heap.pop ();
if (st[t]) continue;
st[t] = true;
for (int i = h[t];~i;i = ne[i]) {
int j = edges[i].to,w = edges[i].w > max_w;
if (dist[j] > dist[t] + w) {
dist[j] = dist[t] + w;
heap.push ({dist[j],j});
}
}
}
return dist[n] <= k;
}
int main () {
memset (h,-1,sizeof (h));
scanf ("%d%d%d",&n,&m,&k);
int max_w = 0;
while (m--) {
int a,b,c;
scanf ("%d%d%d",&a,&b,&c);
max_w = max (max_w,c);
add (a,b,c),add (b,a,c);
}
int l = 0,r = max_w + 1;
while (l < r) {
int mid = l + r >> 1;
if (check (mid)) r = mid;
else l = mid + 1;
}
if (l == max_w + 1) printf ("-1\n");
else printf ("%d\n",l);
return 0;
}
农夫约翰正在一个新的销售区域对他的牛奶销售方案进行调查。
他想把牛奶送到 T 个城镇,编号为 1∼T。
这些城镇之间通过 R 条道路 (编号为 1 到 R) 和 P 条航线 (编号为 1 到 P) 连接。
每条道路 i 或者航线 i 连接城镇 Ai 到 Bi,花费为 Ci。
对于道路,0≤Ci≤10,000 然而航线的花费很神奇,花费 Ci 可能是负数(−10,000≤Ci≤10,000)。
道路是双向的,可以从 Ai 到 Bi,也可以从 Bi 到 Ai,花费都是 Ci。
然而航线与之不同(单向),只可以从 Ai 到 Bi。
事实上,由于最近恐怖主义太嚣张,为了社会和谐,出台了一些政策:保证如果有一条航线可以从 Ai 到 Bi,那么保证不可能通过一些道路和航线从 Bi 回到 Ai。
由于约翰的奶牛世界公认十分给力,他需要运送奶牛到每一个城镇。
他想找到从发送中心城镇 S 把奶牛送到每个城镇的最便宜的方案。
输入格式
第一行包含四个整数 T,R,P,S
接下来 R 行,每行包含三个整数(表示一个道路)Ai,Bi,Ci
接下来 PP 行,每行包含三个整数(表示一条航线)Ai,Bi,Ci
输出格式
第 1..T 行:第 i 行输出从 S 到达城镇 i 的最小花费,如果不存在,则输出 NO PATH
。
数据范围
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
#include
#include
#include
#include
#include
#define x first
#define y second
using namespace std;
typedef pair PII;
const int N = 25010, M = 150010, INF = 0x3f3f3f3f;
int n, mr, mp, S;
int id[N];
int h[N], e[M], w[M], ne[M], idx;
int dist[N], din[N];
vector block[N];
int bcnt;
bool st[N];
queue q;
void add(int a, int b, int c)
{
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}
void dfs(int u, int bid)
{
id[u] = bid, block[bid].push_back(u);
for (int i = h[u]; ~i; i = ne[i])
{
int j = e[i];
if (!id[j])
dfs(j, bid);
}
}
void dijkstra(int bid)
{
priority_queue, greater> heap;
for (auto u : block[bid])
heap.push({dist[u], u});
while (heap.size())
{
auto t = heap.top();
heap.pop();
int ver = t.y, distance = t.x;
if (st[ver]) continue;
st[ver] = true;
for (int i = h[ver]; ~i; i = ne[i])
{
int j = e[i];
if (id[j] != id[ver] && -- din[id[j]] == 0) q.push(id[j]);
if (dist[j] > dist[ver] + w[i])
{
dist[j] = dist[ver] + w[i];
if (id[j] == id[ver]) heap.push({dist[j], j});
}
}
}
}
void topsort()
{
memset(dist, 0x3f, sizeof dist);
dist[S] = 0;
for (int i = 1; i <= bcnt; i ++ )
if (!din[i])
q.push(i);
while (q.size())
{
int t = q.front();
q.pop();
dijkstra(t);
}
}
int main()
{
cin >> n >> mr >> mp >> S;
memset(h, -1, sizeof h);
while (mr -- )
{
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])
{
bcnt ++ ;
dfs(i, bcnt);
}
while (mp -- )
{
int a, b, c;
cin >> a >> b >> c;
din[id[b]] ++ ;
add(a, b, c);
}
topsort();
for (int i = 1; i <= n; i ++ )
if (dist[i] > INF / 2) cout << "NO PATH" << endl;
else cout << dist[i] << endl;
return 0;
}
C 国有 n 个大城市和 m 条道路,每条道路连接这 n 个城市中的某两个城市。
任意两个城市之间最多只有一条道路直接相连(没有重边)。
这 m 条道路中有一部分为单向通行的道路,一部分为双向通行的道路,双向通行的道路在统计条数时也计为 1 条。
C 国幅员辽阔,各地的资源分布情况各不相同,这就导致了同一种商品在不同城市的价格不一定相同。
但是,同一种商品在同一个城市的买入价和卖出价始终是相同的。
商人阿龙来到 C 国旅游。
当他得知“同一种商品在不同城市的价格可能会不同”这一信息之后,便决定在旅游的同时,利用商品在不同城市中的差价赚一点旅费。
设 C 国 n 个城市的标号从 1∼n,阿龙决定从 1 号城市出发,并最终在 n 号城市结束自己的旅行。
在旅游的过程中,任何城市可以被重复经过多次,但不要求经过所有 n 个城市。
阿龙通过这样的贸易方式赚取旅费:他会选择一个经过的城市买入他最喜欢的商品——水晶球,并在之后经过的另一个城市卖出这个水晶球,用赚取的差价当做旅费。
因为阿龙主要是来 C 国旅游,他决定这个贸易只进行最多一次,当然,在赚不到差价的情况下他就无需进行贸易。
现在给出 n 个城市的水晶球价格,m 条道路的信息(每条道路所连接的两个城市的编号以及该条道路的通行情况)。
请你告诉阿龙,他最多能赚取多少旅费。
注意:本题数据有加强。
输入格式
第一行包含 2 个正整数 n 和 m,中间用一个空格隔开,分别表示城市的数目和道路的数目。
第二行 n 个正整数,每两个整数之间用一个空格隔开,按标号顺序分别表示这 n 个城市的商品价格。
接下来 m 行,每行有 3 个正整数,x,y,z 每两个整数之间用一个空格隔开。
如果 z=1,表示这条道路是城市 x 到城市 y 之间的单向道路;如果 z=2 表示这条道路为城市 x 和城市 y 之间的双向道路。
输出格式
一个整数,表示答案。
数据范围
1≤n≤100000
1≤m≤500000
1≤各城市水晶球价格≤100
输入样例:
5 5
4 3 5 6 1
1 2 1
1 4 1
2 3 2
3 5 1
4 5 2
输出样例:
5
最一般的最短路维护的是路径上的sum性质,本题维护的是max和min性质,sum性质具有累加性(就是要从前面的值基础上累加,后续出现只会越来越大,所以第一次出现的就是最短),而max 和min对于新出现的数,单独比较即可,所以不能用dijkstra(dijkstra就是利用的sum的累加性)
总的来说就是max和min,后面出现的数不一定比前面的数都差(而dijkstra的sum性质能保证后面出现的数都比前面的数都差)
为什么不能使用Dijkstra
能使用Dijkstra的原则是,当我们从小根堆拿出距离最小的点,更新其他的邻接点的边,是可以保证它未来不会被其他点更新的更小的。固非负权图可以满足,因为但凡后面的点还能到此点,只能让距离不变或更大。
那这道题,题目没有保证没有环,那么找寻最小的情况,可能第一次到达点 i,此时它前面的点更新到它,最小的确定出来了是dmin[i],使用 dijkstra更新完后如果真的把它锁定,日后仍可能遇到更小的点,且能走回dmin[i],从某种角度讲,这道题并不是看边权,而是看点权,故只要走到一个相对比之前dmin[i]小的点,且还能走回i,就相当于有了负权边,故此时dijkstra 失效。
为什么必须反向建图
由图结构到DP
我们这道题首先根据题意判断,价值首先不在边权,而是点权。其次本题说所有点可以经过 无限 次,故并不是一次最短路解决问题。说明存在非最短路最优的情况,要么需要改造最短路算法,要么需要组合一些别的算法。
因为阿龙主要是来 C 国旅游,他决定这个贸易只进行最多一次,当然,在赚不到差价的情况下他就无需进行贸易。故直觉思路就是找寻所有到 N 的路径,然后把路径中最大的点权减去最小的点权。
使用 DP 的思想,我们可以找寻到达点 i的最小点权【包括i】,然后找寻从点i到达点n的最大点权,然后进行求差值。
那为什么反向建图?只用正向图难道不能算i到n了不成?
有这个疑问的,需要好好思考一下,最短路算的距离,最后得到的d[n],是1 -> n的最短路,我们初始化距离为无穷,然后初始化起点为 0,所以最短路算法算的是固定起点,算到所有终点的距离。
故我们如果想用正向图算i -> n,那岂不是每枚举到一个点 i,都需要算一次正向最短路,才能算出 i -> n。
但如果反向建图求最短路
我们反向建图,可以求得n -> 任何点的最短路,但我们知道,实际的边是正向的,故反向图的n -> 任何点的最短路,等价于实际的正向图任何点 -> n的最短路。
故我们使用了反向图,就把一个O(n ^ 2)问题,变成了O(2n)的问题。这里指的是两个建图方式的最短路复杂度对比,而不是实际的复杂度。
实际情况下,瓶颈是SPFA,SPFA算法的时间复杂度是O(km),其中k一般情况下是个很小的常数,最坏情况下是n,n表示总点数,m表示总边数。因此总时间复杂度是 O(km),至于两次spfa也算是常数,就归入 k 中了。
正向图正着走一遍 =(-)倒着走一遍
正向图正着走一遍=反向图倒着走一遍
#include
#include
#include
#include
using namespace std;
const int N=1e5+10,M=2e6+10,INF=0x3f3f3f3f;
//h[]正向图 hr[]反向图
int h[N],hr[N],e[M],ne[M],idx;
int price[N];
bool st[N];
//从1~i 过程中,买入水晶球的最低价格 dmin[],从i~n过程中卖出水晶球的最高价格
int dmin[N],dmax[N];
//建图
void add(int h[],int a,int b)
{
e[idx]=b;
ne[idx]=h[a];
h[a]=idx++;
}
int n,m;
void spfa(int d[],int start,int h[],bool flag)
{
queueq;
memset(st,0,sizeof st);
if(flag) memset(d,0x3f,sizeof dmin);
q.push(start);
d[start]=price[start];
st[start]=true;
while(q.size())
{
int t=q.front();
q.pop();
st[t]=false;
for(int i=h[t];i!=-1;i=ne[i])
{
int j=e[i];
if(flag&&d[j]>min(d[t],price[j])||!flag&&d[j]
#include
#include
using namespace std;
vectorway_min[110000];
vectorway_max[110000];//我这里用vector记录数组,大家可以使用邻接矩阵
int value[110000], mins[110000], maxs[110000];
//value[i]代表城市i的水晶球价格
//mins[i]表示从1走到i的过程中,买入水晶球的最低价格
//maxs[i]表示从i走到n的过程中,卖出水晶球的最高价格
void dfs_min(int x, int m)
{
if(m >= mins[x]) return;//该点已有更优解,无需再次搜索
m = min(m, value[x]);
mins[x] = m;//更新最优解
for(int i = 0; i < way_min[x].size(); i++)
{
dfs_min(way_min[x][i], m);//遍历相邻的点
}
}
void dfs_max(int x, int m)//这个函数和上一个函数差不多,这里不再赘述
{
if(m <= maxs[x]) return;
m = max(m, value[x]);
maxs[x] = m;
for(int i = 0; i < way_max[x].size(); i++)
{
dfs_max(way_max[x][i], m);
}
}
int main()
{
int n, m, x, y, z, ans = 0;
cin >> n >> m;
for(int i = 1; i <= n; i ++)
{
cin >> value[i];
mins[i] = 999999999;
maxs[i] = -999999999;//先取一下极值,也就是初始化
}
for(int p = 1; p <= m; p++)
{
cin >> x >> y >> z;
way_min[x].push_back(y);//这里表示求最小值所需的路径
way_max[y].push_back(x);//因为最大值是从n开始(从i到n可转化为从n到i),所以我们把所有路径倒个头
if(z == 2)//双向边
{
way_min[y].push_back(x);
way_max[x].push_back(y);
}
}
dfs_min(1, value[1]);//从1开始遍历最小值
dfs_max(n, value[n]);//从n开始遍历最大值
for(int i = 1; i <= n; i ++) ans = max(ans, maxs[i] - mins[i]);
cout << ans << endl;
return 0;
}