【团体程序设计天梯赛】L3-028 森森旅游

森森从1号城市出发,先用现金支付路费,到某个城市后将所剩全部现金换成旅游金,用旅游金支付剩下的路费,直至到达n号城市。也就是说,我们要判断每一个转折城市k,求【1号城市到k号城市所需现金】+【k号城市到n号城市所需旅游金/k号城市的汇率】,值最小的那个,就是我们所求答案。

其实也是求两个单源最短路问题,一个是点1到各点所需的最少现金,另一个则是点n到各点所需的最少旅游金(前面分析的是说各点到点n的最少旅游金,但用n次dijkstra时间复杂度太高,而且我们只需要各点到点n的距离,各点到其他点的距离是没有用的)。第一个最短路我们以现金为距离建立正向图即可,第二个最短路我们以旅游金为距离建立反向图,注意需要两个head数组。由于点较多,我们可以用小根堆优化的dijkstra算法。

处理完两个距离数组后,我们就可以求解了。由于每次查询我们只需输出最少的现金数(最优解),因此我们仍可以用小根堆优化,先求出更新汇率前的所有解,加入堆。每次更新的时候,求出新解,入堆,小根堆的堆顶就是我们要的答案(但要及时弹出过时的答案,因为每次汇率更新都是建立在之前汇率调整的基础上的)。

#include <iostream>
#include <cstring>
#include <queue>
#define N 100010
#define M 400010
#define inf 0x3f3f3f3f3f3f3f3fll
using namespace std;
typedef long long LL;

struct node{//优先队列结点:{距离,中间点}
    LL dist;
    int id;
    bool operator < (const node& a) const{//小根堆
        return dist>a.dist;
    }
};
int n,m,q,a[N],vis[N];
int h[2][N],ver[M],nxt[M],w[M],tot;//h[0][k]是现金正向图,h[1][k]是旅游金反向图
LL d1[N],d[N];//d1是1到各点的现金距离,d是n到各点的旅游金距离

void add(int u,int v,int c,int k){
    ver[++tot]=v; w[tot]=c;
    nxt[tot]=h[k][u]; h[k][u]=tot;
}

void dijk(int s,int k){
    memset(d,0x3f,sizeof d);
    memset(vis,0,sizeof vis);
    priority_queue<node> pq;//堆优化
    node t,r; 
    t.dist=d[s]=0;
    t.id=s;
    pq.push(t);
    
    while(pq.size()){//dijkstra
        t=pq.top(); pq.pop();
        int u=t.id;
        if(vis[u]) continue;
        vis[u]=1;
        
        for(int i=h[k][u];i;i=nxt[i]){
            int v=ver[i]; 
            if(!vis[v]&&d[v]>d[u]+w[i]){
                r.dist=d[v]=d[u]+w[i]; 
                r.id=v;
                pq.push(r);
            }
        }
    }
    if(!k) memcpy(d1,d,sizeof d);
}

int main(){
    scanf("%d%d%d",&n,&m,&q);
    while(m--){
        int u,v,c,d;
        scanf("%d%d%d%d",&u,&v,&c,&d);
        add(u,v,c,0),add(v,u,d,1);//现金正向图,旅游金反向图
    }
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    dijk(1,0);//求1到各点的现金距离
    dijk(n,1);//求n到各点的旅游金距离
    
    priority_queue<node> pq;//堆优化
    node t;
    for(int i=1;i<=n;i++){//未调整时所有方案所需现金
        if(d1[i]!=inf&&d[i]!=inf){
            t.dist=d1[i]+(d[i]+a[i]-1)/a[i];//1用现金到i,从i用旅游金到n(等价于反向图n用旅游金到i)
            t.id=i;
            pq.push(t);
        }
    }
    while(q--){
        int x;
        cin>>x>>a[x];
        if(d1[x]!=inf&&d[x]!=inf){//更新距离
            t.dist=d1[x]+(d[x]+a[x]-1)/a[x];
            t.id=x;
            pq.push(t);
        }
        while(pq.size()){
            t=pq.top();
            x=t.id;
            if(t.dist!=d1[x]+(d[x]+a[x]-1)/a[x]) pq.pop();//及时排除过时信息
            else break;
        }
        printf("%lld\n",t.dist);//堆顶值就是答案,注意,不需要弹出
    }
    return 0;
}

  • 12
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值