基于经纬度和工作日的拜访轨迹问题

一 问题描述

设有 n 家客户,n <= 24,每个客户有如下属性(客户名称、客户类型、经度、纬度、每月拜访次数)。

每个客户 m 天拜访 1 次。

每个客户每天最多拜访 8 次。

给出一个月的工作日列表。

求一个月拜访轨迹。

二 输入和输出

1 输入说明

第 1 行代表客户数 n 和拜访频率 m。

接下来的 n 行描述每个客户,每个客户信息包括(客户名称、客户类型、经度、纬度、每月拜访次数),客户类型有:M-商业拜访,H-医院拜访、P-药房拜访

接下来的一行是工作日列表

2 输出说明

药代这个月每天拜访轨迹。

三 输入和输出样例

1 输入样例

24 3

H1 H 117.196067 39.103303 8

H2 H 117.206175 39.111509 8

H3 H 117.187551 39.109744 8

H4 H 117.179981 39.113295 8

H5 H 117.183536 39.125713 8

H6 H 117.209626 39.12228 8

H7 H 117.233087 39.082267 8

H8 H 117.195568 39.111846 8

H9 H 117.207421 39.121483 8

H10 H 117.191717 39.104914 8

H11 H 117.200753 39.109458 8

H12 H 117.182597 39.102957 8

H13 H 117.188228 39.128191 8

H14 H 117.193003 39.121008 8

H15 H 117.190858 39.135014 8

H16 H 117.179979 39.11038 8

M1 B 117.18461 39.117381 4

M2 B 117.206176 39.127932 4

M3 B 117.185073 39.124452 4

M4 B 117.183584 39.121472 4

M5 B 117.172522 39.126702 4

M6 B 117.185248 39.104909 4

M7 B 117.192618 39.129049 4

M8 B 117.181509 39.123574 4

1 2 3 4 5 7 8 9 10 11 12 14 15 16 17 18 19 21 22 23 24 25 26 28 29 30

2 输出样例

1 号拜访客户:H9 > H6 > M2 > H15 > M8 > M5 > H3 > H12 >

2 号拜访客户:H4 > H16 > M6 > H10 > H1 > M3 > H13 > H7 >

3 号拜访客户:M7 > H5 > M4 > M1 > H14 > H8 > H11 > H2 >

4 号拜访客户:M2 > H6 > H9 > H15 > M8 > M5 > H3 > H12 >

5 号拜访客户:H1 > H10 > M6 > H16 > H4 > M3 > H13 > H7 >

7 号拜访客户:H14 > M4 > M1 > H5 > M7 > H8 > H11 > H2 >

8 号拜访客户:H15 > M8 > M5 > H3 > H12 > H9 > H6 > M2 >

9 号拜访客户:H7 > H1 > H10 > M6 > H16 > H4 > M3 > H13 >

10 号拜访客户:H5 > M4 > M1 > H14 > M7 > H8 > H11 > H2 >

11 号拜访客户:H12 > H3 > M8 > M5 > H15 > M2 > H6 > H9 >

12 号拜访客户:H7 > H1 > H10 > M6 > H16 > H4 > M3 > H13 >

14 号拜访客户:H8 > H11 > H2 > H14 > M4 > M1 > H5 > M7 >

15 号拜访客户:H6 > H9 > H15 > H3 > H12 >

16 号拜访客户:H7 > H1 > H10 > H16 > H4 > H13 >

17 号拜访客户:H8 > H11 > H2 > H14 > H5 >

18 号拜访客户:H3 > H12 > H9 > H6 > H15 >

19 号拜访客户:H16 > H4 > H10 > H1 > H13 > H7 >

21 号拜访客户:H8 > H11 > H2 > H14 > H5 >

22 号拜访客户:H9 > H6 > H15 > H3 > H12 >

23 号拜访客户:H13 > H4 > H16 > H10 > H1 > H7 >

24 号拜访客户:H5 > H14 > H8 > H11 > H2 >

25 号拜访客户:H3 > H12 > H9 > H6 > H15 >

26 号拜访客户:H4 > H16 > H10 > H1 > H13 > H7 >

28 号拜访客户:H8 > H11 > H2 > H14 > H5 >

四 代码

package com.platform.modules.alg.alglib.sdgt0002;

import java.util.*;

public class Sdgt0002 {
    public String output = "";
    /**
     * 默认地球半径
     */
    private static double EARTH_RADIUS = 6371000; // 赤道半径(单位m)
    private final int inf = 0x3f3f3f3f;
    // 客户数
    private int n;
    // 拜访频率,几天拜访一次
    private int frequency;
    // 拜访天数,每月拜访多少天
    private int days;
    // 客户批次 map,第1个泛型代表第几批客户,第2个泛型代表批次客户列表
    private Map<Integer, List<Customer>> batchCustomerMap = new HashMap<>();
    // 拜访轨迹 map,第1个泛型代表每月第几天,第2个泛型代表该天拜访的客户列表
    private Map<Integer, List<Customer>> visitMap = new HashMap<>();

    // 轨迹计算
    public String cal(String input) {
        String[] line = input.split("\n");
        String[] params = line[0].split(" ");
        n = Integer.parseInt(params[0]); // 客户数
        frequency = Integer.parseInt(params[1]); // 几天拜访一次
        //days = Integer.parseInt(params[2]); // 每月拜访多少天
        // 对输入数据进行处理
        for (int i = 1; i <= n; i++) {
            String[] customerStr = line[i].split(" ");
            Customer customer = new Customer();
            customer.setName(customerStr[0]); // 客户名称
            customer.setType(customerStr[1]); // 客户类别
            customer.setLocationLon(Double.parseDouble(customerStr[2])); // 经度
            customer.setLocationLat(Double.parseDouble(customerStr[3])); // 纬度
            customer.setTotalCount(Integer.parseInt(customerStr[4])); // 每月最多拜访次数
            // 批次客户列表加第1个客户,如果 frequency = 3,则批次下标范围 [0,1,2]
            if (batchCustomerMap.get(i % frequency) == null) {
                List<Customer> batchCustomerList = new ArrayList();
                batchCustomerList.add(customer);
                batchCustomerMap.put(i % frequency, batchCustomerList);
            } else { // 批次客户列表加其他客户
                batchCustomerMap.get((i % frequency)).add(customer);
            }
        }
        // 模拟每月的几号
        String[] days = line[n + 1].split(" ");
        List batchDays[] = new ArrayList[frequency];
        for (int i = 0; i < batchDays.length; i++) {
            batchDays[i] = new ArrayList();
        }
        for (int i = 0; i < days.length; i++) {
            int batchNum = i % frequency;
            batchDays[batchNum].add(days[i]);
        }

        // 每日拜访处理
        for (int i = 0; i < days.length; i++) {
            int batch = 0;
            for (List batchDay : batchDays) {
                if (batchDay.contains(days[i])) {
                    batch = i % frequency;
                    break;
                }
            }
     
            // 获取某批次客户列表
            List<Customer> customers = batchCustomerMap.get(batch);
            // 打乱批次客户列表顺序
            Collections.shuffle(customers);
            List<Customer> selectCustomers = new ArrayList<>();
            // 选择前8个客户
            for (int j = 0; j < customers.size(); j++) {
                if (customers.get(j).getTotalCount() > 0) {
                    selectCustomers.add(customers.get(j));
                    if (selectCustomers.size() >= 8) {
                        break;
                    }
                }
            }
            // 每日拜访的第1个客户
            if (selectCustomers.size() == 0) continue;
            Customer startCustomer = selectCustomers.get(0);
            // 标识该客户已访问
            startCustomer.setVisited(true);
            // 修正剩余可拜访次数
            int plusCount = startCustomer.getTotalCount() - 1;
            startCustomer.setTotalCount(plusCount);
            // 创建第 i 号的拜访客户列表
            List<Customer> oneDayCustomerList = new ArrayList();
            visitMap.put(i, oneDayCustomerList);
            // 向 i 号拜访客户列表加第1个客户
            oneDayCustomerList.add(startCustomer);
            Customer firstCustomer; // 两次相邻拜访的前一个客户
            Customer secondCustomer; // 两次相邻拜访的后一个客户
            Customer nextSelectCustomer = new Customer(); // 下一个选中的客户

            double tempMinDistance; // 临时最短距离
            firstCustomer = startCustomer;
            for (int m = 1; m < selectCustomers.size(); m++) { // 从第2个客户开始处理
                double minDistance = inf; // 初始化最短距离
                for (int n = 1; n < selectCustomers.size(); n++) { // 从第2个客户开始处理
                    secondCustomer = selectCustomers.get(n);
                    // 两次相邻拜访的后一个客户如果已经拜访或剩余可拜访次数为0,不处理
                    if (secondCustomer.isVisited() || secondCustomer.getTotalCount() == 0) {
                        continue;
                    }
                    // 计算相邻两个客户的距离
                    tempMinDistance = GetDistance(firstCustomer.getLocationLon(), firstCustomer.getLocationLat(), secondCustomer.getLocationLon(), secondCustomer.getLocationLat());
                    if (tempMinDistance < minDistance) {
                        // 更新最短距离
                        minDistance = tempMinDistance;
                        // 最短距离对应的客户
                        nextSelectCustomer = secondCustomer;
                    }
                }

                // 下一个被选中的客户设置为已拜访
                nextSelectCustomer.setVisited(true);
                int plusTimes = nextSelectCustomer.getTotalCount() - 1;
                // 下一个被选中的客户剩余拜访次数
                nextSelectCustomer.setTotalCount(plusTimes);
                // 向 i 号拜访客户列表加选中的客户
                visitMap.get(i).add(nextSelectCustomer);
                firstCustomer = nextSelectCustomer;
            }
            // visited 只对当天拜访起作用,所以要清空 visited 标识
            for (Customer selectCustomer : customers) {
                selectCustomer.setVisited(false);
            }
        }

        // 输出处理
        for (int i = 0; i < days.length; i++) {
            List<Customer> customers = visitMap.get(i);
            if (customers == null) {
                continue;
            }
            output += days[i] + " 号拜访客户:";
            for (Customer customer : customers) {
                output += customer.getName() + " > ";
            }
            output += "\n";
        }

        return output;
    }

    /**
     * 功能描述:通过经纬度计算两点之间的距离
     *
     * @param lon1 第 1 个点的经度
     * @param lat1 第 1 个点的纬度
     * @param lon2 第 2 个点经度
     * @param lat2 第 2 个点纬度
     * @return 两点间的距离
     * @author chengqiuming
     * @date 2022/10/25
     * @description:
     */
    public double GetDistance(double lon1, double lat1, double lon2, double lat2) {
        double radLat1 = rad(lat1);
        double radLat2 = rad(lat2);
        double a = radLat1 - radLat2;
        double b = rad(lon1) - rad(lon2);
        double s = 2 * Math.asin(Math.sqrt(Math.pow(Math.sin(a / 2), 2) + Math.cos(radLat1) * Math.cos(radLat2) * Math.pow(Math.sin(b / 2), 2)));
        s = s * EARTH_RADIUS;
        s = Math.round(s * 10000) / 10000;
        return s;
    }

    /**
     * 转化为弧度(rad)
     */
    private static double rad(double d) {
        return d * Math.PI / 180.0;
    }
}

class Customer {
    // 客户名称
    private String name;
    // 客户类型 H-医院客户 M-商业公司客户 P-药房拜访
    private String type;
    // 经度
    private Double locationLon;
    // 纬度
    private Double locationLat;
    // 每月最多可拜访次数
    private int totalCount;
    // 是否已拜访
    private boolean visited = false;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    public Double getLocationLon() {
        return locationLon;
    }

    public void setLocationLon(Double locationLon) {
        this.locationLon = locationLon;
    }

    public Double getLocationLat() {
        return locationLat;
    }

    public void setLocationLat(Double locationLat) {
        this.locationLat = locationLat;
    }

    public int getTotalCount() {
        return totalCount;
    }

    public void setTotalCount(int totalCount) {
        this.totalCount = totalCount;
    }

    public boolean isVisited() {
        return visited;
    }

    public void setVisited(boolean visited) {
        this.visited = visited;
    }
}

五 测试

1 输入

24 3

H1 H 117.196067 39.103303 8

H2 H 117.206175 39.111509 8

H3 H 117.187551 39.109744 8

H4 H 117.179981 39.113295 8

H5 H 117.183536 39.125713 8

H6 H 117.209626 39.12228 8

H7 H 117.233087 39.082267 8

H8 H 117.195568 39.111846 8

H9 H 117.207421 39.121483 8

H10 H 117.191717 39.104914 8

H11 H 117.200753 39.109458 8

H12 H 117.182597 39.102957 8

H13 H 117.188228 39.128191 8

H14 H 117.193003 39.121008 8

H15 H 117.190858 39.135014 8

H16 H 117.179979 39.11038 8

M1 B 117.18461 39.117381 4

M2 B 117.206176 39.127932 4

M3 B 117.185073 39.124452 4

M4 B 117.183584 39.121472 4

M5 B 117.172522 39.126702 4

M6 B 117.185248 39.104909 4

M7 B 117.192618 39.129049 4

M8 B 117.181509 39.123574 4

1 2 3 4 5 7 8 9 10 11 12 14 15 16 17 18 19 21 22 23 24 25 26 28 29 30

2 输出

1 号拜访客户:H15 > M8 > M5 > H3 > H12 > H9 > H6 > M2 >

2 号拜访客户:H1 > H10 > M6 > H16 > H4 > M3 > H13 > H7 >

3 号拜访客户:H14 > M4 > M1 > H5 > M7 > H8 > H11 > H2 >

4 号拜访客户:M2 > H6 > H9 > H15 > M8 > M5 > H3 > H12 >

5 号拜访客户:H13 > M3 > H4 > H16 > M6 > H10 > H1 > H7 >

7 号拜访客户:H14 > M4 > M1 > H5 > M7 > H8 > H11 > H2 >

8 号拜访客户:H6 > H9 > M2 > H15 > M8 > M5 > H3 > H12 >

9 号拜访客户:M3 > H13 > H4 > H16 > M6 > H10 > H1 > H7 >

10 号拜访客户:M1 > M4 > H5 > M7 > H14 > H8 > H11 > H2 >

11 号拜访客户:M8 > M5 > H15 > M2 > H6 > H9 > H3 > H12 >

12 号拜访客户:M3 > H13 > H4 > H16 > M6 > H10 > H1 > H7 >

14 号拜访客户:H8 > H11 > H2 > H14 > M4 > M1 > H5 > M7 >

15 号拜访客户:H15 > H9 > H6 > H3 > H12 >

16 号拜访客户:H7 > H1 > H10 > H16 > H4 > H13 >

17 号拜访客户:H5 > H14 > H8 > H11 > H2 >

18 号拜访客户:H12 > H3 > H9 > H6 > H15 >

19 号拜访客户:H1 > H10 > H16 > H4 > H13 > H7 >

21 号拜访客户:H11 > H2 > H8 > H14 > H5 >

22 号拜访客户:H9 > H6 > H15 > H3 > H12 >

23 号拜访客户:H4 > H16 > H10 > H1 > H13 > H7 >

24 号拜访客户:H2 > H11 > H8 > H14 > H5 >

25 号拜访客户:H12 > H3 > H9 > H6 > H15 >

26 号拜访客户:H1 > H10 > H16 > H4 > H13 > H7 >

28 号拜访客户:H11 > H2 > H8 > H14 > H5 >

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值