2020华为codeforce比赛总结

160 阅读7分钟

最终提交代码

import java.util.*;

/**
 * @author SJ
 * @date 2020/12/19
 */
public class NewSolution {

    /**
     * --------------------------------基石----------------------------------------
     */
    public static Scanner scanner = new Scanner(System.in);
    public static List<Server> serverList = new ArrayList<>();//存储所有的服务器信息
    public static List<VMs> vms = new ArrayList<>();//存储所有的虚拟机信息
    public static int step = 1;//总共需要几步
    public static List<List<List<Integer>>> ans = new ArrayList<>();//存入具体的步骤

    //虚拟机类
    public static class VMs {
        public int ID;
        public int needCores;//占用核心数
        public int needRAM;//占用内存
        public int location;//当前位置
        public int destination;//需要移动到的位置
        public int STATE = 0;//=0 表示在正确的位置 =1表示等待目标服务器分配资源转出


        public VMs(int ID, int needCores, int needRAM) {
            this.ID = ID;
            this.needCores = needCores;
            this.needRAM = needRAM;
        }

        //输入虚拟机信息
        public static void input(int N) {
            for (int i = 0; i < N; i++) {
                //  System.out.println("输入核心数和内存");
                //虚拟机要存的地方:大的虚拟机列表,对应服务器优先队列,目标服务器的期待列表,当前服务器的转出列表
                int nC = scanner.nextInt();
                int nM = scanner.nextInt();
                VMs vm = new VMs(i, nC, nM);
                vms.add(vm);

            }
            for (int i = 0; i < N; i++) {
                //  System.out.println("输入移动");
                int curLocation = scanner.nextInt();
                int des = scanner.nextInt();
                VMs tempVM = vms.get(i);
                tempVM.location = curLocation;
                tempVM.destination = des;
                Server myServer = serverList.get(curLocation);
                if (curLocation != des) {
                    tempVM.STATE = 1;
                    serverList.get(des).wantVM.add(tempVM);
                    myServer.isNotMyVM.add(tempVM);
                } else
                    completeIndex.add(i);


                //更新服务器
                myServer.coresLeft -= tempVM.needCores;
                myServer.ActuCoreLeft -= tempVM.needCores;
                myServer.RAMLeft -= tempVM.needRAM;
                myServer.ActuRAMLeft -= tempVM.needRAM;

            }

        }
    }

    //服务器类
    public static class Server {
        public int ID;//编号
        public int totalCores;//总核心数
        public int totalRAM;//总内存数
        public int coresLeft;//剩余核心数;
        public int RAMLeft;//剩余内存数
        public int process = 0;
        public int ActuCoreLeft;
        public int ActuRAMLeft;
        public List<VMs> wantVM = new LinkedList<>();//期待的虚拟机
        public List<VMs> isNotMyVM = new LinkedList<>();//暂存在这里的虚拟机需要移除的虚拟机


        public Server(int ID, int totalCores, int totalRAM, int coresLeft, int RAMLeft, int actuCoreLeft, int actuRAMLeft) {
            this.ID = ID;
            this.totalCores = totalCores;
            this.totalRAM = totalRAM;
            this.coresLeft = coresLeft;
            this.RAMLeft = RAMLeft;
            ActuCoreLeft = actuCoreLeft;
            ActuRAMLeft = actuRAMLeft;

        }


        //输入数据
        public static void input(int N) {

            for (int i = 0; i < N; i++) {
                int tC = scanner.nextInt();
                int tR = scanner.nextInt();
                serverList.add(new Server(i, tC, tR, tC, tR, tC, tR));
            }
        }

    }


    /**
     * --------------------------------常用工具--------------------------------
     */
    //存入已到位的虚拟机编号
    public static Set<Integer> completeIndex = new HashSet<>();

    //看看这一step满没满
    public Boolean check() {
        if (ans.size() == step && ans.get(step - 1).size() == serverList.size())
            return true;
        else
            return false;
    }

    //是否所有任务都已完成
    public Boolean isMissionCompleted() {
        return completeIndex.size() == vms.size();
    }

    //服务器排序
    public int demand(Server server) {
        int sum = 0;
        List<VMs> wantVM = server.wantVM;
        for (VMs vMs : wantVM) {
            sum += vMs.needRAM;
        }
        return sum - server.RAMLeft;
    }

    Comparator<Server> serverComparator = new Comparator<Server>() {
        @Override
        public int compare(Server o1, Server o2) {
            return demand(o2) - demand(o1);
        }
    };

    //虚拟机排序
    Comparator<VMs> vMsComparator = new Comparator<VMs>() {
        @Override
        public int compare(VMs o1, VMs o2) {
            return o2.needRAM - o1.needRAM;
        }
    };
    
    /**
     * --------------------------核心方法--------------------------------
     */

    //移动虚拟机
    Boolean changeFlag = false;

    public Boolean move(Server serverOne, Server serverTwo, VMs vm) {
        //从一台虚拟机移动到另一台虚拟机需要进行的操作
        //1.判断当前虚拟机进程数,=2则返回
        //2.目标虚拟机内存和核心数够不够 不够则返回
        //如果可以移动
        //1.两个服务器进程数量+1
        //2.更新服务器的内存和核心数
        //3.修改对应服务器的期待和出列表
        //4.在one的优先队列出队
        //5.在two的优先队列入队
        //6.更新虚拟机的所在地,更新虚拟机状态。

        if (serverOne.process == 2 || serverTwo.process == 2
                || serverTwo.ActuRAMLeft < vm.needRAM
                || serverTwo.RAMLeft < vm.needRAM
                || serverTwo.ActuCoreLeft < vm.needCores
                || serverTwo.coresLeft < vm.needCores
                || vm.destination == vm.location//已经在家了,不许跑
                || serverOne.ID == serverTwo.ID //自己移动自己
                || (ans.size() == step && ans.get(step - 1).size() == serverList.size())//已经N步
        )
            return false;
        changeFlag = true;
        serverOne.process++;
        serverTwo.process++;
        serverChanged.add(serverOne.ID);
        serverChanged.add(serverTwo.ID);
        serverOne.coresLeft += vm.needCores;
        serverOne.RAMLeft += vm.needRAM;
        serverTwo.coresLeft -= vm.needCores;
        serverTwo.RAMLeft -= vm.needRAM;

        vm.location = serverTwo.ID;
       /* serverTwo.myVM.add(vm);
        serverOne.myVM.remove(vm);*/
        //到了目标位置
        if (serverTwo.ID == vm.destination) {
            completeIndex.add(vm.ID);
            // vm.STATE = 0;
            serverOne.isNotMyVM.remove(vm);
            //  System.out.println(remove);
            serverTwo.wantVM.remove(vm);
            //  System.out.println(remove1);

        } else {//移到别人家
            serverOne.isNotMyVM.remove(vm);
            serverTwo.isNotMyVM.add(vm);

        }


        //记录这一步
        if (ans.size() < step) {
            List<List<Integer>> oneStepAns;
            oneStepAns = new ArrayList<>();
            oneStepAns.add(Arrays.asList(serverOne.ID, serverTwo.ID, vm.ID));
            ans.add(oneStepAns);
        } else {
            ans.get(step - 1).add(Arrays.asList(serverOne.ID, serverTwo.ID, vm.ID));
        }
        return true;

    }

    //释放资源
    public static Set<Integer> serverChanged = new HashSet<>();

    public void releaseResource() {
        for (Integer index : serverChanged) {
            Server server = serverList.get(index);
            server.process = 0;
            server.ActuCoreLeft = server.coresLeft;
            server.ActuRAMLeft = server.RAMLeft;
            //更新虚拟机状态
            // updateServerState(server);
            changeFlag = false;


        }
        serverChanged.clear();
        step++;

    }


    /**
     * ---------------------------------解锁-------------------------------
     */
    public static Set<Integer> vmChanged = new HashSet<>();//移动过的虚拟机
    public static Set<Integer> serverOut = new HashSet<>();//负责出的服务器
    public static Set<Integer> serverIn = new HashSet<>();//负责进的服务器

    public void openLock2() {

        int unCom = vms.size() - completeIndex.size();//未到位的虚拟机个数
        int N = serverList.size();
        int times = Math.min(unCom / 2 + 1, N / 2);
        // int times = N/2;


        for (int i = 0; i < serverList.size() && times != 0; i++) {

            Server server = serverList.get(i);

            if (serverIn.contains(server.ID))//用来进的虚拟机不允许再出
                continue;

            for (int i1 = 0; i1 < server.isNotMyVM.size(); i1++) {

                VMs vMs = server.isNotMyVM.get(i1);


                if (vmChanged.contains(vMs.ID))//移动过的虚拟机不允许再动
                {
                    continue;
                } else {


                    Boolean serverIn = false;

                    serverIn = findServerIn(server, vMs);


                    if (serverIn) {

                        serverOut.add(server.ID);//记录腾出空间的虚拟机
                        times--;//完成一次移动


                    }

                }


            }

        }


    }

    public void startAfterOpenLock() {


        for (Integer integer : serverOut) {
            Server server = serverList.get(integer);
            List<VMs> wantVM = server.wantVM;

            if (wantVM.size() == 0)
                continue;
            else {
                wantVM.sort(vMsComparator);
                for (int i = 0; i < wantVM.size(); i++) {
                    VMs vMs = wantVM.get(i);
                    move(serverList.get(vMs.location), server, vMs);
                }
            }
        }
        releaseAfterLock();

    }

    public void releaseAfterLock() {
        serverIn.clear();
        serverOut.clear();
    }

    public Boolean findServerIn(Server curServer, VMs vm) {

        //    getServersChooseIn.sort(serverComparator.reversed());
        for (Server server : serverList) {
            if (serverOut.contains(server.ID)) {//腾出位置的虚拟机不允许再进
            } else if (move(curServer, server, vm)) {
                serverIn.add(server.ID);//记录用来进的虚拟机
                return true;
            }
        }


        return false;
    }


    /**
     * ----------------------------正式操作----------------------------------------
     */
    public static List<Server> serversChoose = new ArrayList<>();//severlist的副本,用来排序

    public void start() {
        serversChoose.addAll(serverList);


        int N = serverList.size();
        while (!isMissionCompleted()) {

            int code = work(N);
            if (code == -1)//任务结束
                return;
            else if (code == -2) {//出现死锁
                openLock2();

                if (check())
                    releaseResource();
                startAfterOpenLock();

            }
            if (!check() && code != -2)
                releaseResource();
            else if (check())
                releaseResource();


        }


    }

    public int work(int N) {
        //返回值-1 表示任务结束
        //返回值-2 表示出现死锁
        if (isMissionCompleted())
            return -1;


        //最多N步,死锁就跳出,遍历虚拟机
        int instep = 0;//记录步数

        serversChoose.sort(serverComparator);

        for (Server server : serversChoose) {

            List<VMs> isNotMyVM = server.isNotMyVM;
            isNotMyVM.sort(vMsComparator);
            for (int i = 0; i < isNotMyVM.size(); i++) {
                VMs vMs = isNotMyVM.get(i);
                if (move(server, serverList.get(vMs.destination), vMs))
                    instep++;

            }
            if (instep == N)
                return 0;
        }


        if (isMissionCompleted())
            return -1;
        if (!changeFlag)//出现死锁
        {
            return -2;
        }
        return instep;


    }

    public static void main(String[] args) {
        NewSolution newSolution = new NewSolution();
        int N = scanner.nextInt();
        int M = scanner.nextInt();
        Server.input(N);
        VMs.input(M);
        newSolution.start();


        //输出答案
        //  System.out.println(step - 1);
        System.out.println(ans.size());
        for (List<List<Integer>> an : ans) {
            System.out.println(an.size());
            for (List<Integer> list : an) {
                for (Integer integer : list) {
                    System.out.print(integer + " ");
                }
                System.out.println();
            }
        }
    }


}

策略总结:

解锁策略:

point:177002.276
破环:每次移动一半的待解锁(移动的次数不超过服务器的个数)
选择策略:for循环遍历服务器,放得下就放进去

point:181031.018
破环后不释放资源继续处理剩下的队列;
问题出在,破环时步数服务器已达到上限,再处理也是没用。

point:225130.514
解两遍锁,不排斥分配到目标位置

point:177229.358
假设待处理队列是服务器其总数的N倍,就解N/2遍锁

point:186444.959
假设待处理队列是服务器其总数的N倍,就解N遍锁

point:  223599.501
建立服务器的优先队列,每次选择服务器时都要更新,超时

point: 176838.023177002.276的基础上放弃优先队列,改用普通队列

point:176620
将服务器分为两类:只有出任务的服务器和其他服务器,出现死锁时,优先将造成死锁的任务倒到只有出任务的服务器里,
当死锁时 如果队列里的数字小于服务器个数,move 1/2

point:220181.018
当死锁时 如果队列里的数字小于服务器个数,就全部move  证明比移动一半差太多


point:176923.738
当死锁时 如果队列里的数字小于服务器个数,就move 1/3

point:176530.585176620的基础上(有这么个问题,先选择只有出任务的服务器,如果出任务的服务器里没地方放,也进不去其他服务器里找位置)
该掉了这个问题,变成优先出任务服务器,然后其他 都能找到

point:176530.585
把所有的队列改成list,方便索引

point:176776.811
充分利用step  不仅在出现死锁的时候解锁,在每一个step里的小step没用完的时候也解锁

point:292303.196
在上一步的基础上,每次解锁选择时,如果没有在只有出任务的服务器里找到课以移动的位置,就对服务器的剩余容量进行由大到小排序,
从最大的开始尝试放置;(程序出问题了,全超时)
问题出在,两个服务器之间的移动操作,不是更新的servers列表里服务器的值 程序崩溃了

point:快逃!
重构代码:
 对于正常的一步中选择虚拟机开始执行的顺序:只入>有出有入>只出
 死锁后服务器的选择:优先选择最拥挤的服务器开始出虚拟机
 对于造成死锁后的虚拟机选择目标服务器的顺序:完成>只出>有出有入>只入
 选中对应的服务器后选择服务器内虚拟机的顺序:1.是否要转出(要转出的排在前面)2.需要RAM越小越靠前 3.需要核心数越小越靠前


为了解决摇摆问题
解锁时从某一个虚拟机的isNotmyVM 里开始出虚拟机  出去的虚拟机在这个解锁步骤中不允许再移动
并且解锁步骤中的虚拟机只能出不能入。 为了把空间腾出来 。直到完成了一个解锁步骤N步或者所有被锁住的虚拟机都移动了一遍


point:174416.615

每个服务器内存储:1. 在本服务器内但不属于本服务器的虚拟机列表。每次出虚拟机时从这个列表里出 2.本该在本服务器但还没有到达的虚拟机名单

从前向后遍历服务器(按原本的ID顺序遍历,未排序),在约束条件以内能出则出

当出现死锁时:安置max(处于死锁状态的虚拟机个数/2,N) 台虚拟机
从前向后遍历服务器列表(按原本的ID顺序遍历,未排序)
对处于死锁中的虚拟机进行安置  某虚拟机 从 本服务器到B服务器  对于B服务器的选择也是从前向后遍历,放的下就放

安置过程中,保证若A服务器中有虚拟机出来,那么本次解锁过程中就不会有其他虚拟机进来
也就是说,1.解锁过程中没有虚拟机能回家 2.如果这台服务器有虚拟机出来,那么这台服务器在本趟解锁过程中只能用来出虚拟机(最大限度地腾出空间)
3.用来进虚拟机的服务器本次也只能用来进(如果这台服务器也出虚拟机的话,反正这虚拟机也回不了家,只会白白的增加内存移动消耗)

当没有虚拟机可以移动时,解锁结束(有使解锁结束的两种情况 1.达到了任务次数 2.所有的服务器都被贴上了出或入的标签 此时 出集合里没有能到入集合的虚拟机了)

此时一趟解锁结束。

解锁结束后,遍历“出”服务器集合中的服务器(这些服务器已经腾出空间了),每个服务器有一个本该在本服务器但还没有到达的虚拟机名单,让名单上的这些虚拟机回家

然后开始下一次遍历虚拟机。

point:173248.869
执行时按照代入RAM总和-本服务器剩余容量 降序, (最优)

point:176725.954
按照待出列表总和降序

point:175107.878
按照剩余容量

point:173289.99173248.869的基础上 解锁时也按照待入RAM总和-本服务器剩余容量 降序

point:175092.161
按期望进出差绝对值排序

point:
每动一步排个序,4个超时

point:
每动一个虚拟机排个序

point:174272.668
从前往后找和从后往前找同时进行

point:188372.414
不解锁的情况下。有四个未通过:10 13 15 23

point:173262.564
对每个虚拟机的需要内存进行降序,

point:173574
在上一步的基础上,先执行状态位为2的虚拟机 先让大的进来


调试解锁:
 int times = Math.max(unCom / 5, N/2);    174237
 int times = Math.max(unCom / 5, N);       174237
 int times = Math.Min(unCom / 2+1, N);  174218.501
 int times = Math.Min(unCom / 3+1, N);  174269.241
 int times = Math.Min(unCom / 2+1, N/2);  174218.501(最优)
 int times = Math.Min(unCom / 2+1, N/3); 174218.435
 最优time下死锁时找剩余空间最大的进行入:  174176.711
 最优time下死锁时找待进-left 最小的的进行入:  174181




point:173255.23
当前最优策略:
每个step 虚拟机排序 +虚拟机内vm排序 +解锁Math.Min(unCom / 2+1, N/2) 次

如果不对vm排序:173322.314,且时间还变多了

173275: 最优策略的基础下死锁时对xxx进行拍