UVALive 4625 Garlands(二分答案 + DP)

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

思路:如果单纯从DP方面考虑,那么设状态量 d[ i ][ j ] 表示前 i 个,分成 j 段的最小值,那么复杂度是 O(n*m*d)肯定爆掉。所以这道题有一个非常巧妙的解法,那就是二分答案

当确定当前二分值 x 时,对原序列进行DP,设d[ i ] 表示前分 i 个数字的半段最大值都不超过 x 的最小段数,然后 d[ i ] = min(d[ i ] ,d[ j ] +1),i、j之间满足上述的偶数、d、x这些条件 ,可以枚举半段长度 len 。但是这样之后还有一个问题,那就是对于第三组样例  1 1 100 100 1 1 ,m = 3(即分为2段),会发现,如果 x == 102 ,那么它的最小段数是 1 ,能分成 1 段,并不意味着它一定能分成 2 段。这里还有一点,那就是奇偶性,对于已经分出的一段,它的长度是偶数,我们找到它的中点,从中点向两边再分出 1 段,那么就相当于把本来的1 段分成了 3 段,增加了 2 段,如果你直接把两边那两段直接搞成长度是 2 的,那么就可以一直那么分下去,也就是说,奇数段一定能分成奇数段,偶数段一定能分成偶数段。所以,我们再进行上述DP的时候,还要在开一维,用来表示奇偶,状态转移方程为 : d[ i ][ j ] =  min( d[ k ][ ~j ] + 1 ),i、k 满足关系。

代码如下:

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

const int INF = 0x0fffffff ;

const int MAXN = 40004 ;

int w[MAXN],sum[MAXN];

int n,m,d;

int dp[MAXN][2];

int check(int x)
{
    dp[0][0] = 0;
    dp[0][1] = INF;
    for(int i = 2 ; i <= n ; i += 2)
    {
        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] > x ) break;
            if(sum[i - len] - sum[i - 2*len] <= x)
            {
                dp[i][0] = min(dp[i][0],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) return 0;
    else 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)
        {
            puts("BAD");
        }
        else if(n < 2*(m-1))
        {
            puts("BAD");
        }
        else if(n > 2*d*(m-1))
        {
            puts("BAD");
        }
        else
        {
            int l = 1, r = sum[n];
            while(l<r)
            {
                int m = l+r >>1;
                if(check(m))
                {
                    r = m;
                }
                else l = m+1;
            }
            printf("%d\n",l);
        }
    }
    return 0;
}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值