题目描述
N 个点,P 条边,求 1 到 N 的最小花费,如果经过的边数小于 K ,花费为 0 ,否则花费为 K+1 大的边权值
样例
5 7 1
1 2 5
3 1 4
2 4 8
3 2 3
5 2 9
3 4 7
4 5 6
如图:
```
分层图最短路
什么是分层图:
当同一个点可以有不同的操作时,我们将一个点分开,对应不同的操作,再重新与其他点相连
我们对每个点都进行如此操作后,原本只有一层的图就被我们分层了
我们把样例分层,如图:
```
红边是普通边,其他颜色的边是进行免费操作的边
分层图的作用
经过分层后,我们得到了新图
我们可以发现,原本题目中选 k 条边免费的操作被我们等价了:
在从一个点到另一个点时,如果选择免费,就进入下一层,相当于进行一次免费操作
因为可以免费 k 次,所以我们要建 k+1 层图
在 k+1 层图上我们已经不能再往下了,即免费操作已用完
那么,如何建边呢?
代码实现:
for(int i=1,x,y,z;i<=p;i++)
{
scanf("%d%d%d",&x,&y,&z);
add(x,y,z);
add(y,x,z); //本层建边
for(int j=1,z1=0;j<=k;j++)
{
add(x+(j-1)*n,y+j*n,z1); //第j层和第j+1层间的建边
add(y+(j-1)*n,x+j*n,z1);
add(x+j*n,y+j*n,z);
add(y+j*n,x+j*n,z); //第j+1层建边
}
}
关于本题
那么,本题已经很明显可以用分层图做
但是
由于最终花费是权值最大的边
所以,在更新最短路的操作中,需要略微变动
以前我们是这么更新的:
if ( dis[y] > dis[x] + z ) dis[y] = dis[x] + z;
现在我们要这么更新:
z=max(edge,dis[x]); //edge是当前边权值
if ( dis[y] > z ) dis[y] = z;
AC代码
#include<bits/stdc++.h>
using namespace std;
const int N=1000000+10,M=10000000+10;
int n,p,k;
int tot=0;
priority_queue< pair<int ,int> > q;
struct node
{
int ver,nex,edge;
}po[M];
int head[N],dis[N];
bool v[N];
void add(int x,int y,int z)
{
po[++tot].ver=y,po[tot].edge=z;
po[tot].nex=head[x],head[x]=tot;
}
void dijkstra()
{
memset(dis,0x3f,sizeof(dis));
dis[1]=0;
q.push(make_pair(0,1));
while(q.size())
{
int x=q.top().second;
q.pop();
if(v[x]) continue;
v[x]=true;
for(int i=head[x];i;i=po[i].nex)
{
int y=po[i].ver,z=max(po[i].edge,dis[x]);
if(dis[y]>z)
{
dis[y]=z;
q.push(make_pair(-dis[y],y));
}
}
}
}
int main()
{
scanf("%d%d%d",&n,&p,&k);
for(int i=1,x,y,z;i<=p;i++)
{
scanf("%d%d%d",&x,&y,&z);
add(x,y,z);
add(y,x,z);
for(int j=1,z1=0;j<=k;j++)
{
add(x+(j-1)*n,y+j*n,z1);
add(y+(j-1)*n,x+j*n,z1);
add(x+j*n,y+j*n,z);
add(y+j*n,x+j*n,z);
}
}
for(int i=1,z=0;i<=k;i++)
add(i*n,(i+1)*n,z);
//如果读者认真观察代码的话一定会问为什么要有如此操作
//那么读者仔细思考,若无此操作,如果有一组数据 1 到 N 经过的边小于 k ,会发生什么呢?
dijkstra();
if(dis[(k+1)*n]==1061109567) puts("-1");
else printf("%d",dis[(k+1)*n]);
//感谢 Schelski 指出的错误:忘记判路径是否存在了。。。
return 0;
}
贴个spfa的做法
后效性怎么解决的啊? 想不通
int y=po[i].ver,z=max(po[i].edge,dis[x]);
if(dis[y]>z)
{
dis[y]=z;
q.push(make_pair(-dis[y],y));
}
能仔细讲一下这段代码吗,实在是看不懂....
代码中优先队列是大根堆,所以存负值
分层图最短路,是个不错的想法.但是像本题这样层数较多的话,其$O(mk)$的空间可能会被卡
确实是这样,但我看到没人用这种写法,于是来一发
而且个人觉得这样写比较易懂
但主要是当时刚学分层图,遇到最短路题目就想用分层图解~哈哈
先Orz再说
这句话应该没有歧义吧[汗]就是Orz您
直接k+1层建图可能会MLE,如果是多维数组的话,问题就不大了
确实,要是严格按边数取值范围,设置上限1e7就会Memory Limit Exceeded
%%%
边权取反用大根堆 等价于 不取反用小根堆
太妙了,tql
if(dis[y]>z)
{
dis[y]=z;
q.push(make_pair(-dis[y],y));
}
这是什么意思啊 不是要取最大花费吗
因为q是大根堆,要使dis[y]最小就是使-dis[y]最大
给每层终点建一条边是为了防止出现用不到k次就出现最短路的情况么,有点想不明白
是这样的 相当于到了就可以免费去最后一层
分层图的例题可以看一下我整理的题解:https://www.cnblogs.com/littlehb/p/16815959.html
终点是在最后一层,必须k次啊
写的很不错,受益匪浅!膜拜大佬,把你的链接收藏进我的笔记里面了,谢谢大佬!
数据加强了吗? 现在直接复制大佬的代码交都有一组过不了 还有一个疑问 Dijkstra不是不可以处理负权边的吗?
这里放相反数是为了求最大吧
https://blog.csdn.net/malimingwq/article/details/89436697?depth_1-utm_source=
这里有讲为啥不能处理负权边的
取反之后不就变负权了吗 这题这种做法怎么都会错 就是过不了 我最后用的二分+spfa过的
雀氏,二分+spfa简单点,我分层图还没学会(不知道为啥要建下一层到上层的反向边)
但是不是所有的都变负权了么,那不就没关系了,dijstra应该是处理不了负环吧
下层到上层的边不用建 从上层到下层就已经代表了做了一次选择 如果还能回到上层的话会出问题的
雀氏,我也觉得,但是他们题解都建了…比如他这上面的 add(y+(j-1)n,x+jn,z1);
emmm 很巧 我有过和你一样的疑问 看来大家的学习过程都差不多哈哈哈 你可以把这些边都打印出来 debug看起来很麻烦 当你打印出来的时候你这个疑问就会消除了 因为这条边并不是前面那条边的反向边
对哦雀氏不是反向边,hhh我悟了谢谢佬
不是负环,就是处理不了带负权的边,, 取负放优先队列里只是一种技巧,省得写重载小于号而已
数据还是挺弱的,分层图做法你可以看看我这个..
https://www.acwing.com/activity/content/code/content/3199369/
dij是处理不了同时存在异号的边权,建议重新看一下dij不能处理的反例
妙呀
mark
其实这一步不是必须的,一种替代的方法是直接从第0层到第k层统计(最小值)答案
支持
做完最短路之后枚举答案是吗?
“如果经过的边数小于 K ,花费为 0” —> “如果经过的边数小于等于 K ,花费为 0”
Orz
### 我去!Orz
tql%%%
tql
太巧妙了,思路无敌
尤其是这段代码,防止了边数<=k的情况的错误(ORZ)
加上这一段代码就代表着最终答案答案一定在k+1层图下的第n个点也就是dist[(k + 1) * n]
当然也可以不加这一段,在每一层的第n个点找最小的值
不加这一段代码:
不对。你看建图的方式。因为实际上一定会建k+1张图,而每个点都可以使用操作转移到下一张图,也就是说第k+1张图的第n个点,实际上是和第一张图是相连的.在Dijkstra的过程中,会迭代出所有点即所有k+1张图到1号点的最短路
也就是说即使不加 多余的那个代码!!! 第dist[(k+1)*n]他也会被更新出来。
因为只要是连通就会有最短路。
请问空间不会爆吗??这似乎有点大吧。。。
不会,因为 64M ≈ 16777216个int
1600w 差不多1000w个int把
一个结构体数组已经3e7个int了,差不多120MB,明显MLE了吧??