动态规划-罪犯转移问题

1.写在前面的话

        多动脑对身体有好处,我也不知道写的对不对,不当之处请指正。

2.题目

        链接(不知道后面对不对):罪犯转移_牛客题霸_牛客网

        C市现在要转移一批罪犯到D市,C市有n名罪犯,按照入狱时间有顺序,另外每个罪犯有一个罪行值,值越大罪越重。现在为了方便管理,市长决定转移入狱时间连续的c名犯人,同时要求转移犯人的罪行值之和不超过t,问有多少种选择的方式(一组测试用例可能包含多组数据,请注意处理)?

        输入描述:第一行数据三个整数:n,t,c(1≤n≤2e5,0≤t≤1e9,1≤c≤n),第二行按入狱时间给出每个犯人的罪行值ai(0≤ai≤1e9)。输出描述:一行输出答案。

输入:

3 100 2
1 2 3

输出:2

较大数据输入:

12023 222720 2320

106 22 28 77 88 9 59 12 18 109 102 19 58 124 14 0 31 121 120 8 77 1 22 14 76 18 43 61 46 36 113 26 61 48 62 13 30 35 30 122 76 23 70 1 25 18 15 90 108 127 82 104 79 117 0 41 110 32 67 44 26 69 43 96 63 119 63 96 112 55 97 98 117 27 114 121 118 19 5 36 111 65 6 12 59 0 30 109 62 87 48 111 88 112 31 109 98 27 27 32 122 125 107 75 115 77 117 111 90 118 126 100 21 5 36 126 23 60 25 45 82 125 20 84 103 82 81 65 16 125 42 103 106 6 97 35 81 81 101 11 123 122 56 123 16 107 12 86 118 96 122 72 90 5 48 29 76 1 20 101 3 105 86 46 65 127 19 31 16 68 72 20 82 5 69 10 119 92 1 16 127 124 18 26 97 109 58 14 4 65 114 99 19 99 19 44 41 8 40 58 73 116 7 117 112 63 48 119 31 93 58 52 10 37 44 105 103 48 102 123 90 63 62 12 124 94 22 18 4 96 76 113 45 9 126 102 111 28 96 76 46 72 68 85 108 118 66 110 47 0 0 50 126 88 47 20 88 64 54 37 55 4 100 109 42 49 56 127 84 63 28 78 87 94 37 6 64 38 84 39 57 125 55 64 124 111 103 99 113 8 22 44 25 18 104 53 82 13 112 17 4 11 92 14 80 108 24 106 76 47 38 78 92 77 38 76 81 92 121 121 27 71 67 2 118 69 16 9 58 73 17 6 126 126 89 43 88 0 26 39 28 96 73 66 42 30 3 119 108 101 38 107 56 20 40 74 25 48 99 52 126 61 74 115 64 106 35 88 37 6 33 37 5 27 89 9 27 26 111 21 74 64 24 111 52 26 43 23 74 7 112 38 116 40 19 101 127 46 78 123 18 55 5 77 113 50 77 20 47 122 35 37 105 8 46 23 24 80 57 64 8 27 55 54 40 102 87 38 27 78 107 26 4 77 70 64 96 26 48 73 31 59 116 7 24 23 67 77 19 2 2 54 111 56 119 48 44 2 123 84 60 75 116 91 122 31 9 14 18 54 5 49 64 14 87 76 82 66 67 60 89 95 109 74 39 34 69 60 10 100 4 68 26 100 85 97 39 11 106 99 4 103 6 9 57 59 114 114 31 111 51 98 44 100 55 59 110 35 65 92 107 83 10 18 66 113 100 67 19 61 122 117 77 52 93 38 47 91 71 120 39 47 91 57 20 64 66 88 127 73 86 62 25 40 38 14 91 119 74 15 28 73 15 63 6 51 59 77 100 73 125 55 53 49 11 47 109 103 13 122 59 79 51 83 113 97 20 82 19 87 92 116 110 62 42 92 104 59 44 72 10 17 27 52 13 66 112 30 28 37 40 24 41 91 69 11 63 88 108 0 113 46 117 61 63 107 28 65 96 16

输出结果:9704

3.读懂题意

        第一行的3个数字n t c,分别表示n个罪犯、所有转移罪犯可容忍罪行值最大限制t、转移罪犯数c;第二行数据为每个罪犯的罪行值(可以将下标看作罪犯编号,每个值为罪行值);注意转移时,c个罪犯的编号必须是连续的。

4.注意单个罪行值超过最大时的影响

        由于要转移连续的C个罪犯,当一个罪犯的罪行值超过t,将影响他前后c-1个罪犯(连坐)不能转移。

5.动态规划相关

        由于容忍的最大罪行值为t,每选择一个罪犯进行转移时,可容忍的最大罪行值都会减小(动态变化,类似往瓶子里装石头一样),在动态减小过程中,若下一位罪犯的罪行值超过了可容忍的最大罪行值(动态变化的),则对于当前连续的c个罪犯后面的罪犯值,无需再进行判断(大数据量时可避免不少计算,应该是这点跟主旨动态规划比较相关),当前这c个罪犯的选法不符合要求;若都符合条件,则该选法为正确选法之一。

        若不使用上述思想,而是普通的逻辑算法(累加c个罪行值,再判断),虽然也能得出正确结果,但当数据量较大时,运行时间将不达标;附加普通方法如下。

public static void main(String[] args) throws Exception{
    Scanner in = new Scanner(System.in);
    while(in.hasNextLine()){
        String paramStr = in.nextLine();
        int n = Integer.parseInt(paramStr.split(" ")[0]);
        int t = Integer.parseInt(paramStr.split(" ")[1]);
        int c = Integer.parseInt(paramStr.split(" ")[2]);
        int[] weightArray = new int[n];
        if(in.hasNextLine()){
            String dataStr = in.nextLine();
            String[] numStrArray = dataStr.split(" ");
            for(int i=0; i<numStrArray.length && i<n; i++){
                weightArray[i] = Integer.parseInt(numStrArray[i]);
            }
        }
        long startTime = System.currentTimeMillis();
        int transferSelectNum = calcTransferNormal(n, t, c, weightArray);
        System.out.println("计算花费时间:" + (System.currentTimeMillis()-startTime));
        System.out.println(transferSelectNum);
    }
}
 
private static int calcTransferNormal(int n, int t, int c, int[] weightArray) {
    int selectTotal = 0; // 选法数量
    for (int startIndex = 0; startIndex <= n - c; startIndex++) { // 遍历n-c次
        int weightTotal = 0; // 累计权重值
        for (int k = 0; k < c; k++) {
            weightTotal += weightArray[startIndex + k];
        }
        if(weightTotal <= t){
            selectTotal++;
        }
    }
    return selectTotal;
}

       普通方法虽然也能得到正确答案,但当数据量过大时,运行会很长,在牛客网上提交会运行超时。

        略带动态规划思想的方法如下:

    public static void main(String[] args) throws Exception{
        Scanner in = new Scanner(System.in);
        while(in.hasNextLine()){
            String paramStr = in.nextLine();
            int n = Integer.parseInt(paramStr.split(" ")[0]);
            int t = Integer.parseInt(paramStr.split(" ")[1]);
            int c = Integer.parseInt(paramStr.split(" ")[2]);
            int[] weightArray = new int[n];
            if(in.hasNextLine()){
                String dataStr = in.nextLine();
                String[] numStrArray = dataStr.split(" ");
                for(int i=0; i<numStrArray.length && i<n; i++){
                    weightArray[i] = Integer.parseInt(numStrArray[i]);
                }
            }
            long startTime = System.currentTimeMillis();
            // int transferSelectNum = calcTransferNormal(n, t, c, weightArray);
            int transferSelectNum = calcTransfer(n, t, c, weightArray);
            System.out.println("计算花费时间:" + (System.currentTimeMillis()-startTime));
            System.out.println(transferSelectNum);
        }
    }

    private static int calcTransfer(int n, int t, int c, int[] weightArray) {
        int selectTotal = 0;
        for (int startIndex = 0; startIndex <= n - c; startIndex++) { // 遍历n-c次
            if (weightArray[startIndex] > t) { // 单个罪犯罪行值超过最大t,需跳过后续c-1,注意++需多减1
                startIndex = startIndex + c - 2;
                continue;
            }

            if(satisfyCondition(weightArray, startIndex+1, startIndex + c-1, t-weightArray[startIndex])){
                selectTotal++;
            }
        }
        return selectTotal;
    }

    private static boolean satisfyCondition(int[] weightArray, int startIndex, int endIndex, int maxWeight) {
        // 动态规划相关,随着已选择罪行值的增加,可容忍罪行值减少
        for(int i=startIndex; i<=endIndex; i++){
            if(weightArray[i] > maxWeight){ // 当c位罪犯中的某一位罪行值超过可容忍最大值
                return false;
            }else{
                maxWeight -= weightArray[i]; // 前一个罪行值已算入,最大可容忍罪行值减少
            }
        }

        return true;
    }

 

 6.借鉴别人的新解法

           先计算前c个连续罪犯的罪行值,利用连续做文章,若要连续,每次都是整段移动,相当于移除前一个值,再加入后一个值,再进行判断;这种解法运行效率很高,虽然c个罪犯的累计罪行值是动态变化的,但没有很好的体现动态规划的思想。

    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        // 注意 hasNext 和 hasNextLine 的区别
        while (in.hasNextLine()) {
            String paramStr = in.nextLine();
            int n = Integer.parseInt(paramStr.split(" ")[0]);
            int t = Integer.parseInt(paramStr.split(" ")[1]);
            int c = Integer.parseInt(paramStr.split(" ")[2]);
            int[] weightArray = new int[n];
            if (in.hasNextLine()) {
                String dataStr = in.nextLine();
                String[] numStrArray = dataStr.split(" ");
                for (int i = 0; i < numStrArray.length && i < n; i++) {
                    weightArray[i] = Integer.parseInt(numStrArray[i]);
                }
            }
            int transferSelectNum = calcTransferNew(n, t, c, weightArray);
            System.out.println(transferSelectNum);
        }
    }

    /**
     * 转移罪犯新解法
     * 先计算前c个罪犯的总罪行值,后面后移这c个罪犯的下标,相当于加入后一个罪犯,移除最前的罪犯,再次计算判断  
     */
    private static int calcTransferNew(int n, int t, int c, int[] weightArray) {
        int selectWeightSum = 0; // 所有选择罪犯的罪行值总和
        int selectTotal = 0; // 多少种选法

        // 第一种选法,前c个罪犯
        for(int i=0; i<c; i++){
            selectWeightSum += weightArray[i];
        }
        if(selectWeightSum <= t){
            selectTotal += 1;
        }

        // 连续的c个罪犯下标往后移,移除最前,加上最后
        for (int k = c; k < n; k++) { // 遍历n-c次
            selectWeightSum -= weightArray[k-c]; // 移除最前的罪行值
            selectWeightSum += weightArray[k]; // 加上新加入的罪行值
            if(selectWeightSum <= t){
                selectTotal++;
            }
        }
        return selectTotal;
    }

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

kenick

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值