原题链接 Monthly Expense
题目大意:给定n,m,让你将n个数重新划分成m个数,而且是连续才能划分到一起,划分后的值为之前的这些数的和,目标是让重新划分后的这些值中最大的那个最小化。
解题思路:
1. 二分法
容易证明,分成组越少,花费越高,分成组越多,花费越少,呈线性关系,故可以采用二分法求解单调函数极值(最值)。一开始二分的上界为n天花费的总和(相当于分成1份),下界为每天花费的最大值(相当于分成n份),然后二分,每次的mid值为(上界 + 下界)/ 2,然后根据mid值遍历n天花费,对n天的花费进行累加,每当超过mid值 份数++,看看这个mid值能把n天分成几份,如果份数大于m,表示mid偏小,下界 = mid + 1,反之小于等于mid,上界 = mid - 1,然后输出最后的mid值即可,复杂度为 O(nlogM)。
#include<stdio.h>
int n,m,arr[100005],mid;
int vjude()
{
int plane=1,sum=0;//注意这里的plane初值为1,最少一份
for(int i=0; i<n; i++)
{
sum+=arr[i];
if(sum>mid)
{
++plane;
sum=arr[i];//因为mid为最高花费,超过了就要记录到下一组
}
if(plane>m)
{
return 0;//mid低了
}
}
return 1;//mid高了或者刚好合适,都需要降低mid以便找最小值
}
int main()
{
scanf("%d%d",&n,&m);
int low=0,high=0;
for(int i=0; i<n; ++i)
{
scanf("%d",&arr[i]);
high+=arr[i];
if(low<arr[i])
low=arr[i];//最高的单月花费
}
while(low<=high)
{
mid=(high+low)>>1;
if(vjude())
high=mid-1;
else
low=mid+1;
}
printf("%d
",low);
return 0;
}
2. 动态规划(但由于数据规模很大,会超时)。
设best[i][j] 表示把j个单位分成i份使其中最大的一份最小的最优解的最大值,又假设第i份(最后1份)为第k+1个单位到第j个单位的和,显然前面的i-1份包括k个单位。则best[i][j] = min{max(best[i - 1][k], sum(k + 1, j))},其中k<j,sum(l, r)为l到r之间所有单位的和。
//花费-动规算法
#include <iostream>
#include <stdio.h>
#include <string.h>
using namespace std;
int f[1000][1000],n,m,sum[10000],money[10000];
// f[i][j]=min(f[i-1][k],sum[j]-sum[k])
int main()
{
int i,j,k,minx,t,maxn;
scanf("%d%d",&n,&m);
for(i=1; i<=n; i++)
{
scanf("%d",&money[i]);
f[1][i]=f[1][i-1]+money[i];
sum[i]=sum[i-1]+money[i];
}
for(i=2; i<=m; i++)
{
maxn=-1;
for(j=1; j<=i; j++)
maxn=max(money[j],maxn);
for(j=1; j<=i; j++)
f[i][j]=maxn;
for(j=i+1; j<=n; j++)
{
minx=9999999;
for(k=i-1; k<j; k++)
{
t=max(f[i-1][k],sum[j]-sum[k]);
minx=min(t,minx);
}
f[i][j]=minx;
}
}
printf("%d
",f[m][n]);
return 0;
}