【NOIp普及组模拟赛】机密文件 题解

题目

题目描述

        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;
}

        但是,正当我们兴高采烈地去写代码时,我们发现了另一个问题:题目要求我们把每个人都分配上。这时候我们只需要用两个变量维护它的剩余人数和剩余文件数,确保剩余文件数\geqslant剩余人数,才能使每个人都分配到。

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;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值