题目大意:给你一串 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;
}