题目
题目描述
OI总部最近得到可靠消息,近日来怪盗基德会再次来OI总部盗窃机密文件(因为是机密,所以不能透露),所以OIER得在怪盗基德来临之前就把文件备份。不过,正好今天OI总部停电了,所以就得人工抄写了。现在,OI总部内一共有M份资料和K个OIER(S),需要将每一份资料都备份一份,M份资料的页数不一定相同(有不同的,也有相同的)。
现在,你作为其中的一名OIER,把资料分配给OIER备份,由于人太多了,所以每一名OIER所分配到的资料都必须是连续顺序的,并且每一名OIER的备份速度是相同的。
你的任务就是让备份的时间最短,列出最短的方案。数据可能存在多个解,所以,当存在多个解时,让前面的人少备份。
输入
输入文件中的第一行为两个整数M,K,分别表示书本的数目和OIER的人数。
第二行中为由M个分隔的整数构成,分别表示M本书的页数。其中:第i份资料的编号为i。
输出
输出文件中共有K行,每行有两个整数。其中:第i行中表示第i个OIER备份的资料编号的起止。
样例输入
8 3
1 2 3 4 5 6 7 8
样例输出
1 3
4 6
7 8
数据范围
对于50%的数据,满足:1<=k<=m<=500;
对于100%的数据,满足:1<=k<=m<=1000。
题目大意
有k个数和n个人,把这k个数分配给n个人,要求在分配的始终是是连续的情况下,分到最多人最少,并输出每个人从地几本书到第几本书。如果有多组解,要求每个人都必须分到并且前面的人尽可能少。
算法
这道题用二分答案。
首先我们用二分答案查找出它花费的最少时间,然后用贪心算法求出符合要求的解。
先来说二分。我们把每个人分到的尽可能靠近mid,只要可以分完,那么就可行,否则不行。
然后就是贪心构造出符合要求的解了。因为要把前面的尽可能的少,也就是后面的尽可能的多。我们倒叙讨论每个人。我们先把后面的人没取过的文件累加起来(s),然后只要s>mid,就一直从前往后减,这样就能求出符合前面的尽可能小的解。
for (int i=m; i>=1; i--)
{
int s=0,start=1,end=-1;
mm--;
for (int j=1; j<=n; j++)
{
if (vis[j])
{
end=j-1;
break;
}
else
{
s+=a[j];
nn--;
}
}
if (end==-1) end=n;
while (s>mid)
{
s-=a[start];
start++;
nn++;
}
ans[i][0]=start;
ans[i][1]=end;
vis[start]=true;
}
但是,正当我们兴高采烈地去写代码时,我们发现了另一个问题:题目要求我们把每个人都分配上。这时候我们只需要用两个变量维护它的剩余人数和剩余文件数,确保剩余文件数剩余人数,才能使每个人都分配到。
while (s>mid || nn<mm)
{
s-=a[start];
start++;
nn++;
}
这时候我们发现我们可以加一个优化:当剩余人数=剩余文件数时,每个人只分配一个文件即可,也就是分配到的文件编号=人的编号。
if (nn==mm)
{
for (int j=1; j<i; j++) ans[j][0]=ans[j][1]=j;
break;
}
代码
#include <cstdio>
#include <cstring>
using namespace std;
const int N=1005;
int sum,n,m;
int a[N],ans[N][2];
bool vis[N];
int check(int mid)
{
int j=1,ss=sum;
for (int i=1; i<=m; i++)
{
int s=0;
for (;j<=n && s<=mid; j++)
s+=a[j];
j--;
if (s>mid) s-=a[j];
ss-=s;
}
if (ss<=0) return true;
else return false;
}
int main()
{
freopen("secret.in","r",stdin);
freopen("secret.out","w",stdout);
scanf("%d %d",&n,&m);
for (int i=1; i<=n; i++)
{
scanf("%d",&a[i]);
sum+=a[i];
}
int l=1,r=sum,mid;
while (l<r)
{
mid=(l+r)/2;
if (check(mid) && !check(mid-1)) break;
if (check(mid)) r=mid;
else l=mid;
}
memset(vis,false,sizeof vis);
int nn=n,mm=m;
for (int i=m; i>=1; i--)
{
int s=0,start=1,end=-1;
mm--;
for (int j=1; j<=n; j++)
{
if (vis[j])
{
end=j-1;
break;
}
else
{
s+=a[j];
nn--;
}
}
if (end==-1) end=n;
while (s>mid || nn<mm)
{
s-=a[start];
start++;
nn++;
}
ans[i][0]=start;
ans[i][1]=end;
vis[start]=true;
if (nn==mm)
{
for (int j=1; j<i; j++) ans[j][0]=ans[j][1]=j;
break;
}
}
for (int i=1; i<=m; i++) printf("%d %d\n",ans[i][0],ans[i][1]);
return 0;
}