题目描述 Description
现在要把M本有顺序的书分给K个人复制(抄写),每一个人的抄写速度都一样,一本书不允许给两个(或以上)的人抄写,分给每一个人的书,必须是连续的,比如不能把第一、第三、第四本数给同一个人抄写。现在请你设计一种方案,使得复制时间最短。复制时间为抄写页数最多的人用去的时间。
输入描述 Input Description
第一行两个整数M、K;
第二行M个整数,第i个整数表示第i本书的页数。
输出描述 Output Description
共K行,每行两个正整数,第i行表示第i个人抄写的书的起始编号和终止编号。K行的起始编号应该从小到大排列,如果有多解,则尽可能让前面的人少抄写。
样例输入 Sample Input
12 10
384 887 778 916 794 336 387 493 650 422 363 28
样例输出 Sample Output
1 1
2 2
3 3
4 4
5 5
6 6
7 7
8 8
9 9
10 12
数据范围及提示 Data Size & Hint
抄1(K<=M<=100),抄2(K<=1000,<=M<=10000) 抄3(K<=10000,0<=M<=1000000)
第一题可以无脑水,第二题可以DP,第三题的话只能用二分。
这个题二分的部分其实不难,关键在输出。(本蒟蒻交的不带优化的代码,抄1AC,抄2 90, 抄3 80)
1、每一个人的抄写速度都一样
2、现在请你设计一种方案,使得复制时间最短。复制时间为抄写页数最多的人用去的时间。
结合这两句,可以把第二句改成”现在请你设计一种方案,使得抄写页数最多的人抄的页数尽量少“
在判断函数中,对于每一个mid,在for的时候开一个sum=mid,开一个tot记录用了多少人。当sum>=bk[i](第i本书的页数),sum-=bk[i]。当sum不满足条件时,tot++,让sum=mid,
如果tot<=k时,说明当前mid减小(当tot==k时,mid的值加大不会有更优解,所以需要),如果tot>k时,说明当前mid需要增大,这样,开一个haha记录二分出来的抄写页数最多的人抄的页数的最小值,接下来就是神奇的输出了。
方式1:
如果有多解,则尽可能让前面的人少抄写。
对此,我们可以倒着跑一遍,一边跑一边记录区间的值(在我的代码中,我用数组nx1表示区间左边界,数组nx表示区间右边界),同时,开个tot记录区间的大小。
当然,这样可能会出问题。比如样例:
很明显,我们这里的输出是9行,tot=9,而样例里面是k=10,这是错误的。
那怎么办呢?
我们可以加一点小小的特技duang,把两个数组中的大于等于二的区间拆的尽量小
if(tot < k)//
{
int tmp = k;
for(int i = tot;tot < k;i --,tmp--)
{
nx[tmp] = nx[i];//将原数组的元素扔到后面
nx1[tmp] = nx1[i];
if(nx[tmp] - nx1[tmp] > 0)
{
nx[tmp - 1] = nx[tmp];//将此区间右端点左移,成新区间右端点
nx[tmp] = nx1[tmp];//用原区间左端点覆盖原区间右端点(拆成了形如[x,x]的区间)
nx1[tmp - 1] = nx1[tmp] + 1;//此原左端点+1左移,成新区间左端点。
tmp --;//当前的数组tmp和tmp-1都是形如[x,x]的区间
tot ++;//如果到tot==k说明数组已经补全。
}
}
}//关键操作:把前面的大于等于二的区间尽可能拆小区间
请对照下图自行体会
一开始:
tmp==4时:
tmp==4时替换:
tmp==4时替换完成&&本样例完成图:
比较考验思维能力吧….代码很清晰Orz xczhw。
方式2:
void print(int x)
{
int t=n,i,j,s;
for(i=k;i>=1;i--)
{
yi[i]=t;//较大区间
j=t;//某指针
s=0;
while(s+bk[j]<=x&&j>=i)// j>=i的意思是:虽然 i 你能抄这么多书
//但是我们剩下 i-1 个人的书全叫你抄了 我们抄什么?
{
s+=bk[j];//“输出答案时,如果剩余书本数小于等于剩余人数,
//要直接跳到下一个人,之后每人只需抄写一本书即可。”
j--;
}
xi[i]=j+1;
t=j;
}
for(i=1;i<=k;i++)
{
printf("%d %d\n",xi[i],yi[i]);
}
}
举个例子来说,就是第i个人从后面往前抄书最多抄到第i本,此时,剩i-1个人和i-1本书,至少一人一本,如果第i个人抄到了i-1本,则后面的i-1个人分i-2本书的话没法分。
代码很好理解。
依我看来,方式一比较高端,方式二比较简洁。
全代码如下:
#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn=1000000;
int n,k;
int bk[maxn];
int nx[maxn];
int nx1[maxn];
bool can(int x)
{
int tot=0;
for(int i=1;i<=n;)
{
int sum=x;
tot++;
while(sum>=bk[i]&&i<=n)
{
sum-=bk[i];
i++;
}
}
if(tot<=k)
return false;
else
return true;
}
int divv(int l,int r)
{
int ans=0x7fffffff;
while(l<=r)
{
int mid=(l+r)>>1;
if(can(mid))
{
l=mid+1;
}
else
{
r=mid-1;
ans=min(ans,mid);
}
// printf("l:%d r:%d mid:%d\n",l,r,mid);
}
return ans;
}
int xi[1000030],yi[1000030];
void print(int x)
{
int t=n,i,j,s;
for(i=k;i>=1;i--)
{
yi[i]=t;//较大区间
j=t;//某指针
s=0;
while(s+bk[j]<=x&&j>=i)// j>=i的意思是:虽然 i 你能抄这么多书
//但是我们剩下 i-1 个人的书全叫你抄了 我们抄什么?
{
s+=bk[j];//“输出答案时,如果剩余书本数小于等于剩余人数,
//要直接跳到下一个人,之后每人只需抄写一本书即可。”
j--;
}
xi[i]=j+1;
t=j;
}
for(i=1;i<=k;i++)
{
printf("%d %d\n",xi[i],yi[i]);
}
}
int haha;
void printxczhw()
{
int tot=0;
for(int i=n;i>=1;)
{
int sum=haha;
nx[++tot]=i;
while(sum>=bk[i]&&i>=1)
{
sum-=bk[i];
i--;
}
nx1[tot]=i+1;
}
if(tot < k)//
{
int tmp = k;
for(int i = tot;tot < k;i --,tmp--)
{
nx[tmp] = nx[i];//将原数组的元素扔到后面
nx1[tmp] = nx1[i];
if(nx[tmp] - nx1[tmp] > 0)
{
nx[tmp - 1] = nx[tmp];//将此区间右端点左移,成新区间右端点
nx[tmp] = nx1[tmp];//用原区间左端点覆盖原区间右端点(拆成了形如[x,x]的区间)
nx1[tmp - 1] = nx1[tmp] + 1;//此原左端点+1左移,成新区间左端点。
tmp --;//当前的数组tmp和tmp-1都是形如[x,x]的区间
tot ++;//如果到tot==k说明数组已经补全。
}
}
}//关键操作:把前面的大于等于二的区间尽可能拆小区间
for(int i=k;i>=1;i--)
{
printf("%d %d\n",nx1[i],nx[i]);
}
}
int main()
{
scanf("%d%d",&n,&k);
int tt=0;
int minn=0;
for(int i=1;i<=n;i++)
scanf("%d",&bk[i]),minn=max(minn,bk[i]),tt+=bk[i];
haha=divv(minn,tt);
printxczhw();
// print(haha);
return 0;
}
本文介绍了一种通过二分查找和动态规划解决图书最优分配问题的方法,旨在减少图书复制时间,适用于不同规模的数据集。
617

被折叠的 条评论
为什么被折叠?



