UVALive - 4625(dp+二分)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_37632935/article/details/86549866

题目:给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;
}

猜你喜欢

转载自blog.csdn.net/qq_37632935/article/details/86549866
今日推荐