uvalive4625(dp + 二分)

题目大意:
给你一串 n 个数,表示n个球,给你这n个球的重量,要你把这n个数分成 m - 1 段,每段的数字个数都是偶数,对于每一段,它的半段数字个数都不超过d,找出一种分发,使所有的这些半段的重量的最小值,并输出这个最小值。

思路:
详细见代码解释
代码:

#include <iostream>
using namespace std;
#include <cstring>
#include <stdio.h>

const int INF = 0x3f3f3f3f;
const int maxn = 40004;
int w[maxn],sum[maxn];
int n,m,d;
int dp[maxn][2];

int judge(int mid) {
    dp[0][0] = 0;
    dp[0][1] = INF;
    for(int i = 2; i <= n; i+= 2) {//i必须是偶数 题目限制
        dp[i][0] = INF;
        dp[i][1] = INF;
        for(int len = 1; len <= d && i - 2 * len >= 0;len++) {
            if(sum[i] - sum[i - len] > mid)//要直接break 否则会TLE
                break;
            if(sum[i - len] - sum[i - 2 * len] <= mid) {
                dp[i][0] = min(dp[i][0],dp[i - 2 * len][1] + 1);//2*len是一段 那么dp[i][0]表示i分成偶数段 就相当于前i - 2 * len个数字分成奇数段再加上2 * len这一段变成偶数段,即dp[i - 2 * len][1]+1
                dp[i][1] = min(dp[i][1],dp[i - 2 * len][0] + 1);//跟上面同理
            }
        }
    }
    if(dp[n][(m - 1)%2] > m - 1)//如果分成的段数大于m - 1的话就表示说mid太小了
        return 0;
    return 1;
}

int main() {

    int T;
    scanf("%d",&T);
    while(T--) {
        scanf("%d%d%d",&n,&m,&d);
        sum[0] = 0;
        for(int i = 1; i <= n; i++) {
            scanf("%d",&w[i]);
            sum[i] = sum[i - 1] +w[i];
        }
        if(n & 1) //如果n是奇数的话
            printf("BAD\n");
        else if(n < 2*(m - 1))//如果n不足以分成m - 1段
            printf("BAD\n");
        else if(n > 2 * d * (m - 1))//如果n比最长的数字个数还要多
            printf("BAD\n");
        else {//二分查找最小值
            int  l = 1;
            int r = sum[n];
            int mid;
            while(l < r) {
                mid = (l + r)/2;
                if(judge(mid))
                    r = mid;
                else
                    l = mid + 1;
            }
            printf("%d\n",l);
        }
    }
    return 0;
}

你可能感兴趣的:(uvalive4625(dp + 二分))