数据规模

题目里没给出数据规模,我从别的oj网址找的:
所有数据满足 2<=n<=5000 1<=m<=200000 1<=E<=1e7, 1<=ei<=E,所有的E和ei都为实数

用到的知识

最短路算法(spfa,dijkstra都可以)+可持久化堆

解题思路

此题可视为k短路题的模板,由于我讲的可能不是很好,这里推荐一个教程:俞鼎力大牛:堆的可持久化和k短路
由于该教程已经讲的比较细致了,所以我就简单地概括一下,看不懂的地方可以去看教程

构造最短路径树和边的分类

我们先从n点跑一遍最短路,这样就可以得到n为根的最短路径树,然后,我们就可以将边分为树边和非树边
如果一条边(从u到v边权为val)满足:d=val-dist[u]+dist[v],d==0(dist表示到n的距离,这个在跑最短路时可以得到)那么这条边就在树上,否则,d就是我们跑这条边(其他时候沿着树跑)比最短路多出来的距离这是一种情况
我们把所有非树边放到小根堆里,我们就可以得到第2,3短路,但是,这样我们得不到第k短路(k>=4),因为可能连着走两条非树边比走一条非树边更短,所以,我们要把非树边的集合也放进堆里

如何将所有非树边都放到堆里

首先,不是所有的非树边都可以放在同一个集合里的,只有满足一条边的终点在最短路径树上是另一条边的起点的子节点(理解不了可以画个图看看),才能把另一条边放入这条边所在的集合
那么,如何实现找出所有满足条件的集合呢?
我们可以对每个点开一个堆,装这个点出去的所有边,然后将这个点的边集合并到它的子节点上,依次往前合并即可。
注意这个堆的要求:
1.支持合并(左偏树)
2.可持久化(操作不会改变历史版本,即合并时,父节点的集合不受影响,只有子节点的集合受影响)
这就是可持久化左偏树
其实可持久化听起来很难,但是比起普通的左偏树,只是多了一行代码:

int merge(int x,int y){//合并x和y的堆 
    if(!x||!y)return x+y;//有一边为空则返回另一边
    if(tree[x].val>tree[y].val)swap(x,y);//小根堆 
    int p=++size;//与普通左偏树的不同
    tree[p]=tree[x];//可持久化操作,每次只影响新的树,不改变旧树 
    tree[p].rs=merge(tree[p].rs,y);//和右子树合并 
    if(tree[tree[p].ls].len<tree[tree[p].rs].len)swap(tree[p].ls,tree[p].rs);//保持左偏
    tree[p].len=tree[tree[p].rs].len+1;//更新子树长度 
    return p; 
} 

总结

至此,我们已经知道该如何解决k短路问题了,具体步骤如下:
1.从终点跑一遍最短路,得到所有点里终点的距离,并建立最短路径树
2.将每个点的非树边放入这个点的可持久化左偏树中
3.将父亲节点的可持久化左偏树(堆里装与最短路相比需要的额外距离)并入子节点的可持久化左偏树中
4.将起点的可持久化左偏树放入一个堆(普通的小根堆或者小根优先队列都行)
5.每次取堆顶元素,计算得到第k短路,然后将替换一条边和加一条边的额外距离入堆

完整代码

//题目为魔法猪学院 
#include<iostream>
#include<cstdio>
#include<queue>
#include<cstring>
#include<algorithm>
using namespace std;
const int mx_n=5010;
const int mx_m=202002;
int n,m;
double energe;
double dist[mx_n];//到t的距离
struct Edge{
    double val;
    int u,v,next;
};
struct Graph{
    int head[mx_n],top;
    Edge edge[mx_m];
    void addedge(int u,int v,double val){
        edge[++top].val=val;
        edge[top].v=v;
        edge[top].u=u;
        edge[top].next=head[u];
        head[u]=top;
    }
}G,R;//正向图,反向图 
//dijkstra求出dist 
struct cmp{
    bool operator () (const int &a,const int &b)const{
        return dist[a]>dist[b];//小根堆 
    }
};
int nxt[mx_n];//最短路径树上u的下一跳边的编号为nxt[u] 
bool vis[mx_n];
void dijkstra(){
    memset(dist,127,sizeof(dist));
    priority_queue<int,vector<int>,cmp>q;
    q.push(n); //反向跑最短路 
    dist[n]=0;
    while(!q.empty()){
        int u=q.top();
        q.pop();
        if(vis[u])continue;
        vis[u]=1;
        for(int i=R.head[u];i;i=R.edge[i].next){
            int v=R.edge[i].v;
            if(dist[v]>dist[u]+R.edge[i].val){
                dist[v]=dist[u]+R.edge[i].val;
                nxt[v]=i;
                q.push(v);
            }
        }
    }
}
//可持久化左偏树
int root[mx_n]; 
int size=0;
struct Tree{
    double val;//权值(走这条路比最短路长val) 
    int rs,ls;//右,左儿子
    int last;//最后一条不在最短路径树上的边的终点 
    int len;//子树长度 
}tree[mx_m*20];//左偏树(可持久化,空间为原来的logn倍,参考主席树) 
int Newtree(double val,int v){//新建一个树(点) 
    int x=++size;
    tree[x].ls=tree[x].rs=tree[x].len=0;
    tree[x].val=val;
    tree[x].last=v;
    return x;
}
int merge(int x,int y){//合并x和y的堆 
    if(!x||!y)return x+y;//有一边为空则返回另一边
    if(tree[x].val>tree[y].val)swap(x,y);//小根堆 
    int p=++size;
    tree[p]=tree[x];//可持久化操作,每次只影响新的树,不改变旧树 
    tree[p].rs=merge(tree[p].rs,y);//和右子树合并 
    if(tree[tree[p].ls].len<tree[tree[p].rs].len)swap(tree[p].ls,tree[p].rs);//保持左偏
    tree[p].len=tree[tree[p].rs].len+1;//更新子树长度 
    return p; 
} 
struct Path{//优先队列中的路径 
    int last;//路径上最后一个不在最短路径树上的边 
    double ans;//路径的长度(比最短路长的部分) 
    friend bool operator < (const Path &a,const Path &b){
        return a.ans>b.ans;
    }
}; 
//按距离排序,离终点越近越靠前 
int sor[mx_n];//用于按距离排序的数组 
bool CMP (int &a,int &b){
    return dist[a]<dist[b];
}
//main函数
int main(){
    scanf("%d%d%lf",&n,&m,&energe);
    for(int i=1;i<=m;++i){
        int u,v;
        double val;
        scanf("%d%d%lf",&u,&v,&val);
        if(u==n)continue;//起点为n的边是没用的边 
        R.addedge(v,u,val);
        G.addedge(u,v,val);
    }
    dijkstra();
    //从n开始反向跑最短路,生成最短路径树
    for(int i=1;i<=n;++i)sor[i]=i;
    sort(sor+1,sor+1+n,CMP);//按离终点的距离排序,使得子节点的堆先算完,再并入父节点的堆中 
    for(int i=1;i<=n;++i){
        int u=sor[i];
        for(int j=G.head[u];j;j=G.edge[j].next){
            //只要不在最短路径树上,就把这条边放入该点的堆里 
            int v=G.edge[j].v;
            if(nxt[u]!=j)root[u]=merge(root[u],Newtree(G.edge[j].val+dist[v]-dist[u],v));
        }
        if(G.edge[nxt[u]].v)root[u]=merge(root[u],root[G.edge[nxt[u]].v]);
        //父亲节点的堆要并入子节点的堆里 
    } 
    if(dist[1]>energe){//最短路比energe大 
        printf("0");
        return 0;
    }
    energe-=dist[1];
    int ans=1;//最短路算一种方案 
    priority_queue<Path>q;
    if(root[1])q.push((Path){root[1],tree[root[1]].val});//起点的堆中有所有点的堆 
    while(!q.empty()){
        Path now=q.top();
        q.pop();
        if(energe<now.ans+dist[1])break;
        ans++;
        energe-=now.ans+dist[1];
        int x=now.last;//最后一条不在最短路径树上的边
        //左右子树:替换最后一条不在最短路径树上的边 
        if(tree[x].ls){
            q.push((Path){tree[x].ls,now.ans-tree[x].val+tree[tree[x].ls].val});
        }
        if(tree[x].rs){
            q.push((Path){tree[x].rs,now.ans-tree[x].val+tree[tree[x].rs].val});
        }
        //堆顶:新增一条最小的边
        if(root[tree[x].last]){
            q.push((Path){root[tree[x].last],now.ans+tree[root[tree[x].last]].val}); 
        }
    }
    printf("%d",ans);
    return 0;
} 

为了防止你看不懂,我写了很多注释!

吐槽

这题的数据是不是太弱了点,我dijkstra的优先队列写出大根堆都有30分(就是这里,我检查了几个小时硬是没检查出来)