本人也是初学最短路,欢迎交流。
P4568 [JLOI2011]飞行路线
思路:
- 常规求最短路,不过这次可以选择不超过K条边,使这些边的权值变成0;
- 暑假多校遇到的题目,前几天用分层图过了,后面想学最短路+dp,刚好这题又拿出来复习一遍,试试手。
- 关于dij中的松弛操作 d[v] = min( d[u]+w , d[v]) , 这熟悉的模样让人不禁想起dp ,于是就有了dij+dp。
- d[i][j]表示从起点到i点使用了j次优惠的最短路距离,vis[i][j]表示到达i点时是否使用了j次优惠,跟常规的dij不同的是多了一点dp的思想,比如考虑是否进入第j+1层(在经过这条边时享受优惠)或者直接在第j层进行松弛。
代码如下:
#include<bits/stdc++.h>
#define R register int
#define mm make_pair
using namespace std;
const int manx=2e4+5;
const int mamx=manx*6+5; //无向图需要开大一点
int head[manx],d[manx][20]; //d[i][j]表示从起点到i点使用了j次优惠的最短路距离
bool vis[manx][20]; //vis[i][j]表示到达i点时是否使用了j次优惠
struct edge{
int v,w,next;
}a[mamx];
struct node{ //需要判断使用了j次 这里就不用make_pair
int u,w,cnt;
bool operator<(const node &f) const{return w>f.w;}
};
int n,m,s,e,k=0,p; //s为起点 e为终点
void add(int u,int v , int w) //链式前向星
{
a[++k].next=head[u];
head[u]=k;
a[k].w=w;
a[k].v=v;
}
void dij()
{
memset(d,0x3f,sizeof(d)); //下面跟常规dij其实是差不多的
memset(vis,0,sizeof(vis));
priority_queue<node>q;
d[s][0]=0;
q.push((node){s,0,0});
while(q.size())
{
int u=q.top().u, c=q.top().cnt; //这里c是表示使用了c次优惠 或者 在第c层图
q.pop();
if(vis[u][c]) continue;
vis[u][c]=1;
for(int i=head[u];i;i=a[i].next){
int v=a[i].v,w=a[i].w;
if(!vis[v][c+1]&&c<p&& d[v][c+1]>d[u][c]){ //考虑是否使用优惠(进入下一层图)
d[v][c+1]=d[u][c];
q.push((node){v,d[v][c+1],c+1});
}
if(!vis[v][c]&&d[v][c]>d[u][c]+w){ //直接在本层进行操作
d[v][c]=d[u][c]+w;
q.push((node){v,d[v][c],c});
}
}
}
}
int main()
{
scanf("%d%d%d",&n,&m,&p);
cin>>s>>e;
for(int i=1;i<=m;i++)
{
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
add(u,v,w); //注意是无向图
add(v,u,w);
}
dij();
int ans=2147483647; //ans取值很重要,d数组初始化为0x3f,所以尽量取一个比0x3f大的数
for(int i=0;i<=p;i++) //遍历取最小值,因为不一定把p次机会用完
ans=min(ans,d[e][i]);
cout<<ans<<endl;
return 0;
}