首页 > 代码库 > poj1505Copying Books 二分+贪心详细总结
poj1505Copying Books 二分+贪心详细总结
前两天花了时间理解了nyoj的586疯牛和nyoj619青蛙过河,满以为自己能重新写出这道题。。。谁知道。。。。。
题意:有m本书,k个人来抄,每本书有一个书本页数;求使得k个人抄完的最大页数最小,并且每个人都至少要抄一本,然后输出抄书的方案
分析:
这里又涉及到前面nyoj的586疯牛和nyoj619青蛙过河说过的最大值中的最小值, 用前面的例子去理解比较方便
1.我们应该先用二分+贪心算出一个最大页数的最小值--这里和前面的类似
在二分的过程中,我们对于当前考虑的值 x 划分人数的贪心过程中,我们就有flag[i]去标记,这个位置应该划分开,
同时要注意的是:根据题意Output 最后一句If there is more than one solution, print the one that minimizes the work assigned to the first scriber, then to the second scriber etc. 我们可以考虑每一次划分,我们从后面往前面划分:
例如:
5 3
100 100 100 100 100
output
100 / 100 100 / 100 100
而不是
100 100 / 100 100 / 100
2.然后就是根据这个最大的最小值去划分,并且,这里要保证每个人都有书抄,那么在二分+贪心过程中,可能出现得到的人数少于 k
例如:
5 4
100 100 100 100 100
二分的最大的最小值 为 200 那么就会划分成下面的样子
100 / 100 /100 /100 100 红色加粗的那个划分就不会出现,因为是按照200 来划分的,那么此时,我们就应该从左往右开始添加划分,从左往右也是为了保证题意中要求前面的人抄的少的要求
上马:码上注释:
#include <queue> #include <cstring> #include <iostream> #include <algorithm> using namespace std; #define MAX 505 #define LL __int64 int M,k; int book[MAX]; bool flag[MAX];//用来标记 int cnt; int copy(LL x) { cnt = 1; LL sum = 0; memset(flag,false,sizeof(flag)); for(int i = M-1; i >= 0; i --) { sum += book[i]; if(sum > x) { cnt ++; sum = book[i]; flag[i] = true; } } return cnt; } void print() { cout<<book[0]; for(int i = 1; i < M; i ++) { if(flag[i-1]) cout<<" /"; cout<<‘ ‘<<book[i]; } cout<<endl; } int main() { int T; LL l,r; cin>>T; while(T--) { cin>>M>>k; l = r = 0; for(int i = 0; i < M; i ++) { cin>>book[i]; if(book[i] > l) l = book[i]; r += book[i]; } LL mid; while(l < r) { mid = (l+r)/2; if(copy(mid) <= k) r = mid;// - 1 else l = mid + 1; } //得再分一次,不然 5 4 // 100 100 100 100 100 //这个案例就会用上面的 l (不足200) 去切得到 100 / 100 / 100 / 100 / 100 int cnt = copy(r); //对应于下面的案例<2> 可能分得不足k次,那么就把前面的每一本书分给一个人超(题目要求) for(int i = 0; i < M && cnt < k; i ++) { if(!flag[i]) flag[i] = true,cnt++; } print(); } return 0; } /* <1> 7 4 100 100 100 100 100 100 100 <2> 7 6 100 100 100 100 100 100 100 output 100 / 100 100 / 100 100 / 100 100 100 / 100 / 100 / 100 / 100 / 100 100 */个人愚昧观点,欢迎讨论与指正