POJ 3273 解法的讨论

来源:互联网 发布:java时间格式化天 编辑:程序博客网 时间:2024/06/05 11:36
题目描述:
Monthly Expense
Time Limit: 2000MS Memory Limit: 65536KTotal Submissions: 19333 Accepted: 7672

Description

Farmer John is an astounding accounting wizard and has realized he might run out of money to run the farm. He has already calculated and recorded the exact amount of money (1 ≤ moneyi ≤ 10,000) that he will need to spend each day over the next N (1 ≤ N ≤ 100,000) days.

FJ wants to create a budget for a sequential set of exactly M (1 ≤ M ≤ N) fiscal periods called "fajomonths". Each of these fajomonths contains a set of 1 or more consecutive days. Every day is contained in exactly one fajomonth.

FJ's goal is to arrange the fajomonths so as to minimize the expenses of the fajomonth with the highest spending and thus determine his monthly spending limit.

Input

Line 1: Two space-separated integers: N and M 
Lines 2..N+1: Line i+1 contains the number of dollars Farmer John spends on the ith day

Output

Line 1: The smallest possible monthly limit Farmer John can afford to live with.

Sample Input

7 5100400300100500101400

Sample Output

500


1. 动态规划思路

看到关于求解最大最小值,第一反应就是动态规划。这题要求把一串数字连续的分成M个组,使每组和的最大值尽可能的小。以示例数据为例,我们可以这样考虑:假设已经分了4个组,那么答案就应该是前四组中的最大值与剩余数字和最大值中较大的一个,于是会有如下几种情况:


f[i][k]表示直到第i个数字,使用了k个分组,这些组和的最大值

sum(s,e)表示从第s个数字到第e个数字的和

100 400 300 100 | 500 101 400 answer1 = max(f[4][4], sum[5][7])

100 400 300 100 500 | 101 400 answer2 = max(f[5][4], sum[6][7])

100 400 300 100 500 101 | 400 answer3 = max(f[6][4], sum[7][7])

于是可以得到f[7][5] = min(answer1, answer2, answer3)

稍作整理,可以转换为f[7][5] = min(max(f[4+d][4],sum[4+d+1][7])),d=0,1,2


那么f[k][4]如何得到?当然是根据f[k'][3]递推得出的,以f[6][4]为例,步骤如下:

100 400 300 | 100 500 101

100 400 300 100 | 500 101

100 400 300 100 500 | 101

可以看出f[6][4] = min(max(f[3+d][3],sum[3+d+1][6])),d=0,1,2


综合以上例子,我们可以得到递推公式:

f[i][k] = min(max(f[k-1+d][k-1], sum[k+d][i])), d = 0...i-k

对于动态规划的初始状态,可以从只有一个组开始,于是有:

f[i][1] = sum[1][i]


现在可以开始编程了,但还有一个细节,题中N和M都达到了10^5数量级,在C++中会出现数组过大开不出的情况。而我们注意到f[i][k]之与f[i'][k-1]有关,所以可以使用滚动数组的策略,即只声明f[][2],当之前内容使用完后就可以用新的内容覆盖原位置了。

动态规划代码:

/** *POJ-3273-Monthly Expense *xuchen *2015-11-14 21:20:54**/#include "stdio.h"#include <cmath>#include <algorithm>#include <vector>#include <queue>using namespace std;const int N = 100005;int f[N][2];int costs[N];int a(int s, int e){int ans = 0;for(int i=s; i<=e; i++)ans+=costs[i];return ans;}int main(){int n, m;int tmp;int k;scanf("%d%d", &n, &m);for(int i=1; i<=n; i++){scanf("%d", &costs[i]);}for(int i=1; i<=n; i++)f[i][1] = a(1, i);k = 0;for(int t=2; t<=m; t++){for(int i=t; i<=n; i++){f[i][k] = f[i-1][1-k];for(int d = t-1; d<i; d++){tmp = max(f[d][1-k], a(d+1, i));if(tmp < f[i][k])f[i][k] = tmp;}}k = 1-k;}printf("%d\n", f[n][1-k]);return 0;}

很遗憾,由于复杂度近似O(n^2*m),所以TLE了。


2. 二分搜索思路

很多时候,解决问题的方法并不难,难的是如何想到。看到此题的discuss后,有种恍然大悟的感觉,如果采用二分搜索的方法,此题的时间复杂度可以降到O(n*logn),注意,与M无关!

二分搜索的思路是这样的:假设要分成M组,那么我可以先把一个值m作为每个组的上界来对数字就行按顺序划分,如果划分所需要的分组比M大,则说明上界m小了,那就需要再往大了找。如果划分所需要的分组小于等于M,那么当前的m是符合条件的,它可能是最终的解,但我们不确定,因为最终的解也许会是比m小的某个数字,所以我们就需要再往小了找。


首先规定搜索的下界和上界:

low = max(a[i]), 1<=i<=n

up = sum(1, n)

     这比较容易理解,因为组的大小必须不小于最大元素,这样才能放得下,最大为所有元素的和,这是假定只有一个组的情况。

     下面介绍判断m是否可以划分的函数,可以返回true,不可以则返回false:

     

bool isOk(int m){int cnt=1, sum=0;for(int i=1; i<=n; i++){if(sum+a[i] <= m)sum+=a[i];else{cnt++;sum = a[i];}}if(cnt>M)return false;elsereturn true;}

有了这些,我们就可以就行二分搜索了,完整代码如下:

/** *POJ-3273-Monthly Expense *xuchen *2015-11-14 21:20:54**/#include "stdio.h"#include <cmath>#include <algorithm>#include <vector>#include <queue>using namespace std;const int N = 100005;int sum;int a[N];int n, m;bool isOk(int s){int cnt=1, sum=0;for(int i=1; i<=n; i++){if(sum+a[i] <= s)sum+=a[i];else{cnt++;sum = a[i];}}if(cnt>m)return false;elsereturn true;}int main(){int low = 0, up, mid;int minM;scanf("%d%d", &n, &m);for(int i=1; i<=n; i++){scanf("%d", &a[i]);if(low<a[i])low = a[i];sum+=a[i];}up = sum;minM = up;while(low<=up){mid = (up+low)/2;if(isOk(mid)){up = mid-1;minM = mid;}elselow = mid+1;}printf("%d\n", minM);return 0;}


从程序中可以看到,每当得到一个符合划分的mid后,都会先保存起来,因为这很可能就是最后的答案。程序的时间复杂度为O(n*logn)。

0 0
原创粉丝点击