本题要求把一规模为N的数组划分成连续的M份,求最小的每份和限制(限制也即是划分中最大的每份和)。
- 设Limit[x]为划分成x份的最小限制,有Limit[x] >= Limit[x+1],显然份数越大限制是不可能越大的,然后上式的等号是存在的,eg:数组为{1, 1 ,2}时,Limit[2] = Limit[3]。
- 设限制为L,则利用贪心法从前往后计算的份数m:即每超过限制则我们份数加一。
- 贪心中的结论:若份数x < m,则限制Limit[x] > L。此结论可以用归纳法证明,设利用贪心法求得的各份的最后一天分别为Day[i],i = 1,2,...,m。易知到Day[2]为止我们划分为1份的限制肯定是大于L的,则我们假设到Day[i]为止限制为L的最小份数为i(其逆否命题就是前面的结论),显然可以推出到Day[i+1]为止限制为L的最小份数为i+1。
- 由1,2,3我们可以得到二分的思路,设left < mid < right,以mid为限制贪心计算的份数为m。若M < m,则由3可知限制要增大,即left = mid+1,反之限制可能减小也可能不变,则right = mid。
#include<cstdio>
#define maxN 100005
int N,M;
short money[maxN];
short str2num(char* s) //读优化
{
short m = 0;
while(*s)
{
m *= 10;
m += *s-'0';
s++;
}
return m;
}
int calcLimit(int limit)
{
int i;
int monthDay = 1;
int tmpSum = 0;
for(i = 0;i < N;i++) //贪心法计算Limit下的最小划分份数
{
tmpSum += money[i];
if(tmpSum > limit)
{
tmpSum = money[i];
monthDay++;
}
}
return monthDay;
}
int main()
{
int i;
char strIn[7];
int left,mid,right;
while(~scanf("%d%d",&N,&M))
{
left = right = 0;
getchar();
for(i = 0;i < N;i++)
{
gets(strIn);
money[i] = str2num(strIn);
if(left < money[i])
left = money[i];
right += money[i];
}
while(left < right)
{
mid = (left+right)/2;
if(calcLimit(mid) > M) //份数大于M,说明限制比mid大
left = mid+1;
else
right = mid; //限制可能等于mid,也可能比mid小
}
printf("%d\n",left);
}
return 0;
}