-
- 题意:所有的分层图最短路题意基本都一样,给出k次机会将两个点之间的距离/花费变为0,求起始点到终点最短距离/花费,这道题是有向图,别搞得跟我似的没看清题目就瞎存,最后找bug找了两个小时才找出来。
- 题解:先安利一波洛谷各大神仙的题解,我大概就是从这学的:各种题解。
接着自己总结:既然有k次机会可以使用,那可以将原来的图建k+1层(从第0层开始到第k层,每一层的顶点关系都与第0层一致),第i层代表使用免费机会的次数,然后在跑最短路的时候用到dp的思想:
- 若此时免费次数没使用完,并且第t层的结点u到起始点(第0层的s)的最小距离dis[u][t] < 第t+1层结点v(u与v相邻)到起始点(第0层的s)的最小距离dis[v][t+1]那就更新第t+1层结点v到起始点的最小距离;
- 若免费次数已经用完,但是此时并且第t层的结点u到起始点(第0层的s)的最小距离dis[u][t] < 第t层结点v(u与v相邻)到起始点(第0层的s)的最小距离dis[v][t],那相当于此时在同一层进行转移,需要更新第t层结点v到起始点的最小距离。(洛谷题解里面那个图挺生动形象的~)
- 代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
typedef long long ll;
const int N = 1e5 + 5;
const int M = 2e5 + 5;
const int H = 13;
ll t, n, m, k;
ll head[N], vis[N][H], dis[N][H];
int cnt = 0, s = 1; //s:起点
struct Edge
{
ll to, w, next;
}e[M*2]; //无向图边数*2(其实这是有向图...)
struct State
{
ll dis, u, t; //花费/距离、顶点编号、层号
// State(ll dis, ll u, ll t)
// {
// this -> dis = dis;
// this -> u = u;
// this -> t = t;
// }
friend bool operator < (State a, State b) //重载
{
return a.dis > b.dis;
}
};
void add(int u, int v, int w)
{
e[++cnt].next = head[u];
e[cnt].to = v;
e[cnt].w = w;
head[u] = cnt;
}
void dijkstra()
{
dis[s][0] = 0; //起初为第0层,无需消耗k次的免费机会
priority_queue<State> q;
q.push({0, s, 0});
while(q.size())
{
State s = q.top();
q.pop();
ll node = s.u, t = s.t;
if(vis[node][t]) continue;
vis[node][t] = 1;
for(ll i = head[node]; i != -1; i = e[i].next)
{
ll v = e[i].to, w = e[i].w;
if(t < k && dis[node][t] < dis[v][t + 1]) //使用免费通行次数
{
dis[v][t+1] = dis[node][t];
q.push({dis[v][t + 1], v, t + 1});
}
if(dis[node][t] + w < dis[v][t]) //不使用免费通行次数
{
dis[v][t] = dis[node][t] + w;
q.push({dis[v][t], v, t});
}
}
}
}
int main()
{
scanf("%d", &t);
while(t --)
{
cnt = 0;
memset(head, -1, sizeof head);
memset(vis, 0, sizeof vis);
memset(dis, 0x3f, sizeof dis);
scanf("%lld%lld%lld", &n, &m, &k);
for(int i = 1; i <= m; i++)
{
//要去重边?(用邻接表存图不用去)
ll u, v, w;
scanf("%lld%lld%lld", &u, &v, &w);
add(u, v, w);
}
dijkstra();
ll res = 1e15;
for(int i = 0; i <= k; i++) res = min(res, dis[n][i]);
printf("%lld\n", res);
}
return 0;
}
/*
不取min的hack数据
1
3 2 5
1 2 3
2 3 4
*/
-
题目:JLOI2011.飞行路线
- 解析:题意都是一样的,读边的时候习惯从1开始,就进行+1罢了。但是值得注意的是,其实上一题数据有点水,最后其实输出dis[n][k]也能过掉,但是这题数据加强,那么为什么需要最后将使用0次免费机会-> k次机会的结果取一次最小值呢,按照贪心的策略不应该是给的免费机会越多,最后的最小值肯定越小吗?
- 原因:因为若用这种dp转移的方式做分层图最短路,那么当免费次数十分富余时,这时候可能可以使所有道路都免费,那此时的情况将可以视作:用了 k-1(小于k) 次,已达目的地,为了使用最后一次(剩下的)免费通行的权利,先免费跑到另一个地方,结果发现不能免费回来到终点,那岂不是很翻车...所以取min严谨一些。
- 代码:
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 5;
const int M = 2e5 + 5;
const int H = 15;
int dis[N][H], head[N], vis[N][H];
int n, m, k, s, t, cnt = 0;
struct State
{
int dis, u, t;
friend bool operator < (State a, State b)
{
return a.dis > b.dis;
}
};
struct Edge
{
int next, to, w;
}e[M*2];
void add(int u, int v, int w)
{
e[++cnt].next = head[u];
e[cnt].to = v;
e[cnt].w = w;
head[u] = cnt;
}
void dijkstra()
{
memset(dis, 0x3f, sizeof dis);
dis[s][0] = 0;
priority_queue<State> q;
q.push({0, s, 0});
while(q.size())
{
State node = q.top();
q.pop();
int u = node.u, t = node.t;
if(vis[u][t]) continue;
vis[u][t] = 1;
for(int i = head[u]; i != -1; i = e[i].next)
{
int v = e[i].to, w = e[i].w;
if(t < k && dis[u][t] < dis[v][t + 1])
{
dis[v][t + 1] = dis[u][t];
q.push({dis[v][t + 1], v, t + 1});
}
if(dis[u][t] + w < dis[v][t])
{
dis[v][t] = dis[u][t] + w;
q.push({dis[v][t], v, t});
}
}
}
}
int main()
{
memset(head, -1, sizeof head);
scanf("%d%d%d%d%d", &n, &m, &k, &s, &t);
s++, t++;
for(int i = 1; i <= m; i++)
{
int u, v, w;
scanf("%d%d%d", &u, &v, &w);
u++, v++;
add(u, v, w);
add(v, u, w);
}
dijkstra();
int res = 0x3f3f3f3f;
for(int i = 0; i <= k; i++) res = min(res, dis[t][i]);
printf("%d\n", res);
return 0;
}