E. Long Way Home(最短路径/凸优化)

题目
官方题解

题意

给定n个地点(1,2,3,…,n)以及m条无向的路径,并说明了这m条路径所需要的时间。此外,任何两个地点可以通过航班直接抵达,所需要的距离为他们的编号差的平方和(u-v)*(u-v)。

在最多坐k次飞机的情况下,求每个从城市1,到其他城市的最短路径。

思路

1.不考虑航班路线的最短路径

不考虑航班路线,我们可以用dijkstra算出1号城市到其他城市的最短路径。

2.考虑+1次航班路线的最短路径

利用第一步算出的dist,
再考虑,只走一次航班的场景,此时对于节点u,它的最短路径为
d i s n e w [ u ] = m i n ( d i s o l d [ v ] + ( u − v ) ∗ ( u − v ) ) dis_{new}[u] = min(dis_{old}[v]+(u-v)*(u-v)) disnew[u]=min(disold[v]+(uv)(uv))

如何快速查找对应的v,以来确定 d i s n e w [ u ] dis_{new}[u] disnew[u]?我们可以用凸优化,它本质上就是利用了斜率。

对于任意两点v1<v2,u到它们的距离为
d i s o l d [ v 1 ] + ( u − v 1 ) ∗ ( u − v 1 ) , d i s o l d [ v 2 ] + ( u − v 2 ) ∗ ( u − v 2 ) dis_{old}[v1]+(u-v1)*(u-v1),dis_{old}[v2]+(u-v2)*(u-v2) disold[v1]+(uv1)(uv1),disold[v2]+(uv2)(uv2)

如果v2更优,有
d i s o l d [ v 1 ] + ( u − v 1 ) ∗ ( u − v 1 ) > d i s o l d [ v 2 ] + ( u − v 2 ) ∗ ( u − v 2 ) dis_{old}[v1]+(u-v1)*(u-v1) > dis_{old}[v2]+(u-v2)*(u-v2) disold[v1]+(uv1)(uv1)>disold[v2]+(uv2)(uv2)

( d i s o l d [ v 2 ] + v 2 ∗ v 2 ) − ( d i s o l d [ v 1 ] + v 1 ∗ v 1 ) < ( 2 ∗ v 2 − 2 ∗ v 1 ) ∗ u (dis_{old}[v2]+v2*v2) - (dis_{old}[v1] + v1*v1) < (2*v2-2*v1)*u (disold[v2]+v2v2)(disold[v1]+v1v1)<(2v22v1)u

假设存在第3个节点v3,v3>v2,且v3更优,有
( d i s o l d [ v 3 ] + v 3 ∗ v 3 ) − ( d i s o l d [ v 2 ] + v 2 ∗ v 2 ) < ( 2 ∗ v 3 − 2 ∗ v 2 ) ∗ u (dis_{old}[v3]+v3*v3) - (dis_{old}[v2] + v2*v2) < (2*v3-2*v2)*u (disold[v3]+v3v3)(disold[v2]+v2v2)<(2v32v2)u

整理上述两式,有
k 12 = ( d i s o l d [ v 2 ] + v 2 ∗ v 2 ) − ( d i s o l d [ v 1 ] + v 1 ∗ v 1 ) ( 2 ∗ v 2 − 2 ∗ v 1 ) < u k_{12}=\frac{(dis_{old}[v2]+v2*v2) - (dis_{old}[v1] + v1*v1)}{(2*v2-2*v1)} < u k12=(2v22v1)(disold[v2]+v2v2)(disold[v1]+v1v1)<u
k 23 = ( d i s o l d [ v 3 ] + v 3 ∗ v 3 ) − ( d i s o l d [ v 2 ] + v 2 ∗ v 2 ) ( 2 ∗ v 3 − 2 ∗ v 2 ) < u k_{23}=\frac{(dis_{old}[v3]+v3*v3) - (dis_{old}[v2] + v2*v2)}{(2*v3-2*v2)} < u k23=(2v32v2)(disold[v3]+v3v3)(disold[v2]+v2v2)<u

这里的k有实际含义,对于任意两个节点x<y,如果 k x y < u k_{xy}<u kxy<u,则y节点优于x节点,到u的距离更小;反之,如果 k x y > u k_{xy}>u kxy>u,则x节点优于y节点,到u的距离更小。

凸优化:
对于任意3个节点x<y<z,如果 k x y > = k y z k_{xy}>=k_{yz} kxy>=kyz,那么此时,最优节点要么为x,要么为z,一定不是y

证明:

  • u > = k x y > = k y z u>=k_{xy}>=k_{yz} u>=kxy>=kyz时,此时y优于x,z优于y。
  • k x y > = u > = k y z k_{xy}>=u>=k_{yz} kxy>=u>=kyz时,此时x优于y,z优于y。
  • k x y > = k y z > u k_{xy}>=k_{yz}>u kxy>=kyz>u时,此时x优于y,y优于z。

无论哪种场景,y节点做为中间节点,都不能是最优值。

利用凸优化,我们就可以愉快的去掉那些中间节点,最终维护成一个斜率递增的k数组了。然后,对于每个节点i,我们只要从这个递增的k数组中,找到第一个<=i的k斜率,对应的节点,即为我们所求。

这里为什么是第一个<=i的k斜率对应的节点p呢?

对于所有小于i的斜率k[xy],都满足y优于x,x劣于y。
对于所有大于i的斜率k[xy],都满足y劣于x,x优于y。

因此,1劣于2,2劣于3,…,pos-1劣于pos,pos优于pos+1,…,n-1优于n。
在这里插入图片描述

好了,发现最优节点,就是pos。

最后一步,利用凸优化得到的dist,再跑一遍dijkstra,便可以得到多走1次航班路线的最短路径。

3.考虑+k次航班路线的最短路径

学会计算走1次航班路线的最短路径,走k次航班的最短路径,就重复 2过程 k次即可。

代码

代码参考了官方题解

#include <iostream>
#include <vector>
#include <queue>
#include <algorithm>

using namespace std;

#define int long long

const int inf = 1e18;
const int inf1 = 1e15;

struct CHT {
    struct Point {
        int x, y;
        Point() {}
        Point(int _x, int _y): x(_x), y(_y) {}
        // 计算k 这里p节点的位置 大于当前的节点 
        double intersect(Point p) {
            double dy = p.y - y;
            double dx = x - p.x;
            return dy / dx;
        }
        // 计算 dis_old[v] + v*v - 2*uv, 
		// 这里k=u x = dis_old[v] + v*v, y = -2*v
        long long operator () (long long k) {
            return k * x + y;
        }
    };
    
    vector<double> K;
    vector<Point> points;
    
    void init(Point p) {
        K.push_back(-inf);
        points.push_back(p);
    }
    
    void addLine(Point p) {
    	// 存在 K[yz] <= k[xy]的情况 z = p, y = points[sz-1], x = points[sz-2]
    	// 此时丢弃中间节点y 
        while (points.size() >= 2 && p.intersect(points[points.size() - 1]) <= K.back()) {
            K.pop_back();
            points.pop_back();
        }
        if (!points.empty()) {
            K.push_back(p.intersect(points.back()));
        }
        points.push_back(p);
    }
    
    long long query(long long qk) {
    	// 从递增的K序列中,找到最后一个 <= qk 的k[xp]对应的 p节点 
        int id = upper_bound(K.begin(), K.end(), qk) - K.begin();
        --id;
        return points[id](qk);
    }
};
// dijkstra求最短路径 
void dijkstra(vector<vector<pair<int, int>>> &g, vector<int> &dist) {
    const int n = g.size();
    vector<bool> used(n, false);
    priority_queue<pair<int, int>> q;
    for (int i = 0; i < n; ++i) {
        q.push({ -dist[i], i });
    }
    while (!q.empty()) {
        int v = q.top().second;
        q.pop();
        if (used[v]) {
            continue;
        }
        used[v] = true;
        
        for (auto p : g[v]) {
        	int u = p.first;
        	int w = p.second;
            if (dist[u] > dist[v] + w) {
                dist[u] = dist[v] + w;
                q.push({ -dist[u], u });
            }
        }
    }
}

int32_t main() {
    ios_base::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int n, m, k;
    cin >> n >> m >> k;
    vector<vector<pair<int, int>>> g(n);
    for (int i = 0; i < m; ++i) {
        int u, v, w;
        cin >> u >> v >> w;
        --u; --v;
        g[u].push_back({ v, w });
        g[v].push_back({ u, w });
    }
    vector<int> dist(n, inf1);
    dist[0] = 0;
    dijkstra(g, dist);// 计算不做航班的dist 
    for (int i = 0; i < k; ++i) {
    	// 计算坐了 i+1次 航班的dist 
        CHT cht;
        cht.init(CHT::Point(0, 0));
        for (int i = 1; i < n; ++i) {
            cht.addLine(CHT::Point(-i * 2, dist[i] + i * i));
        }
        for (int i = 0; i < n; ++i) {
            dist[i] = cht.query(i) + i * i;
        }
        // 用新的dist计算最短路径 
        dijkstra(g, dist);
    }
    for (int i : dist) {
        cout << i << ' ';
    }
}

最后

觉得文章不错子,可以weixin 搜索 对方正在debug,文章会首发到gongzhonghao上,一起快乐刷题吧~

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
请问以下代码的转发和重定向操作有问题吗:@PostMapping("/updateAppliance") //接口路径 public String updateAppliance(@RequestParam("applianceid") Long applianceId,@RequestParam("appliancename") String applianceName,@RequestParam("appliancepicture") MultipartFile appliancePicture,@RequestParam("aspectstate") String aspectState,@RequestParam("applianceprice") float appliancePrice,@RequestParam("useduration") String useDuration,@RequestParam("functionstate") String functionState,HttpSession session,HttpServletRequest request, HttpServletResponse response) {//传递参数 Seller seller = (Seller) session.getAttribute("seller"); if (seller == null) { //如果卖家没有登录 request.setAttribute("error","请先登陆后再修改家电信息");return "login";} Appliance appliance = new Appliance(); String originalFilename = appliancePicture.getOriginalFilename(); // 获取二手家电图片名 String savePath = "D:/secondhandHA/src/main/webapp/img/"; // 设置图片的保存路径 String picName= System.currentTimeMillis() + originalFilename.substring(original Filename.lastIndexOf("."));//生成新的图片名 File savepicFile = new File(savePath + picName); appliancePicture.transferTo(savepicFile ); // 上传二手家电图片到指定路径 appliance.setAppliancePicture(picName);//获取二手家电图片 appliance.setApplianceName(applianceName);//获取二手家电名称 appliance.setAspectState(aspectState);//获取外观状态 appliance.setAppliancePrice(appliancePrice);//获取二手家电价格 appliance.setUseDuration(useDuration);//获取使用时长 appliance.setFunctionState(functionState);//获取功能状态 appliance.setApplianceId(applianceId);//获取二手家电id int rows = applianceService.updateAppceByid(appliance); //调用applianceService中的updateAppceByid修改二手家电信息,并且返回受影 响的行数 if(rows > 0){ request.setAttribute("success","修改家电信息成功");return "redirect:/ appliancesl";}else{ request.setAttribute("error","修改家电信息失败,请重试");return "updateapp";}} //判断修改家电是否成功
07-25
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值