题目:给n个数,把它们分成m-1段,每段长度不超过d,且每段长度必须为偶数,让你最小化每段的半段权值。(注意是半段)
思路:如果没有半段限制的话,我们可以贪心从左到右将可行的分段尽量拉长。但是加上半段限制后,我们贪心的话就错了。
1
14 5 10
18 11 9 96 3 11 96 67 31 12 58 68 98 76
上面这组数据的答案是110,而从左到右的贪心得到的结果是126。
根据最小化最大值我们很容易想到二分上限,判断可行性,但是判断可行性的时候我们会想到贪心判断,但是对于数据1,1,100,100,1,1 我们把它分成两段1,1,100,100和1,1.最大的半段就是100(注意题目要求每段长度必须是偶数)。如果分成一段
那么最大的半段长度就是102.我们发现随着段数增加,答案可能变大。但是观察发现如果确定段数的奇偶性,随着段数增加,偶段变奇段,奇段变偶段,答案就会变小。所以我们可以设置状态dp[i][0|1]表示把前i个数分成奇数段(1)或偶数段(0)所需的最小段数。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define INF 0x3f3f3f3f
int n,m,d;
int dp[40005][2];
int sum[40005];
int a[400005];
int check(int x)
{
memset(dp,INF,sizeof(dp));
dp[0][0]=0;
for(int i=2;i<=n;i+=2) //前i个,题目要求每段必须是偶数
{
for(int len=1;i-2*len>=0&&len<=d;len++) //len表示半段
{
if(sum[i]-sum[i-len]>x) break;//后半段大于x,直接跳出.
if(sum[i-len]-sum[i-2*len]<=x) //前半段不大于x,这里不能直接跳出,因为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;
return 1;
}
int main()
{
int t;scanf("%d",&t);
while(t--)
{
scanf("%d%d%d",&n,&m,&d);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
sum[i]=sum[i-1]+a[i];
}
int l=0,r=sum[n];
if((n&1)||(n<2*(m-1))||(n>(m-1)*d))//n为奇数,满每段长度为[2,d],m-1段的情况下n是否满足要求。
{
printf("BAD\n");continue;
}
while(l<=r)
{
int mid=(l+r)/2;
if(check(mid))
{
r=mid-1;
}
else l=mid+1;
}
printf("%d\n",l);
}
return 0;
}