UVa 714 - Copying Books
链接:
https://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&problem=655
题意:
把一个包含m个正整数的序列划分成k个(1≤k≤m≤500)非空的连续子序列,使得每个正整数恰好属于一个序列。
设第i个序列的各数之和为S(i),你的任务是让所有S(i)的最大值尽量小。
例如,序列1 2 3 2 5 4划分成3个序列的最优方案为1 2 3 | 2 5 | 4,其中S(1)、S(2)、S(3)分别为6、7、4,
最大值为7;如果划分成1 2 | 3 2 | 5 4,则最大值为9,不如刚才的好。每个整数不超过1e7。
如果有多解,S(1)应尽量小。如果仍然有多解,S(2)应尽量小,依此类推。
分析:
下面考虑一个新的问题:能否把输入序列划分成m个连续的子序列,使得所有S(i)均不超过x?
将这个问题的答案用谓词P(x)表示,则让P(x)为真的最小x就是原题的答案。
P(x)并不难计算,每次尽量往右划分即可(想一想,为什么)。
接下来又可以猜数字了——随便猜一个x0,如果P(x0)为假,那么答案比x0大;
如果P(x0)为真,则答案小于或等于x0。至此,解法已经得出:二分最小值x,把优化问题转化为判定问题P(x)。
设所有数之和为M,则二分次数为O(logM),计算P(x)的时间复杂度为O(n)(从左到右扫描一次即可)。
代码:
1 #include <cstdio> 2 #include <cstring> 3 4 typedef long long int LLI; 5 6 int n, k, a[500+5]; 7 8 bool judge(LLI L){ 9 int amount = 0; 10 LLI sum = 0; 11 for(int i = 0; i < n; i++){ 12 if(sum + a[i] > L){ 13 amount++; 14 sum = 0; 15 } 16 sum += a[i]; 17 } 18 return amount <= k - 1; 19 } 20 21 void output(LLI L){ 22 bool last[500+5]; //last[i] 表示 a[i] 是否是某个划分的最后一个元素 23 memset(last, false, sizeof(last)); 24 int remain = k; 25 LLI sum = 0; 26 for(int i = n - 1; i >= 0; i--){ 27 if(sum + a[i] > L || i + 1 < remain){ 28 last[i] = true; 29 remain--; 30 sum = 0; 31 } 32 sum += a[i]; 33 } 34 35 for(int i = 0; i < n - 1; i++){ 36 printf("%d ", a[i]); 37 if(last[i]) printf("/ "); 38 } 39 printf("%d\n", a[n-1]); 40 } 41 42 int main(){ 43 int T; 44 scanf("%d", &T); 45 while(T--){ 46 int up = -1; 47 LLI sum = 0; 48 scanf("%d%d", &n, &k); 49 for(int i = 0; i < n; i++){ 50 scanf("%d", &a[i]); 51 if(up < a[i]) up = a[i]; 52 sum += a[i]; 53 } 54 55 LLI L = up, R = sum; 56 while(L < R){ 57 LLI M = L + (R - L) / 2; 58 if(judge(M)) R = M; 59 else L = M + 1; 60 } 61 62 output(L); 63 } 64 return 0; 65 }