USACO 1.2.1 Milking Cows 挤牛奶 (80分)

总之:为了节约时间,我开了很多数组来保存一些要用到的量,大概是把导入的时间翻来覆去地处理,那时我隐隐约约感觉到这埋下了不安定的因素。我测试了很久,始终有四个数据点过不了,最后我用测试数据一试,果不其然是数组开得不够大。我设置了两个全局变量分别控制时间数组的最大限度和农民人数的最大限度,而在修改程序的过程中我怎么可能还记得MAXN和MAXT的区别?

对于这道题,我想我们可以先从最简单的思路入手,再不断调整,让程序可以处理各种特殊情况。

假设有这么一张时间表,每一个时刻的状态都有数字表示:有人工作时记作1,没人工作则记作0。 你可以根据输入的信息很容易地生成这么一张表。在这种情况下,你已经可以处理一些基本的情况了,比如一个人在2100开始工作到2300,另一个人从3000开始工作到3200。把这张表打印出来,1的连续区间就是他们有人在工作的情况,而0则表示没有一个人在工作。我们通过数1和数0的个数来计算来计算连续工作或者连续空闲。

仔细想一想,我们的表有一些缺陷,因为会有一些特殊的情况,比如一个人在2100工作到2200,另一个人从2201开始工作到2300。单纯地观察我们的表,这两个人的时段完全是连起来的,而题意要求我们把它断开。所以,我们需要特殊处理这样的情况,我觉得可以把后面这个人的第一个数字不用1表示,而用9,这样就会得到一个类似0000111111119111110000的表,而9把前后两个人的工作给区分开来。 (你需要一个可以方便比较后一个人的开始是不是紧接着前面一个人的终止的办法, 可以用boolean数组来标记时间)

这样做还会有一些问题,因为如果一个人A从2000工作到2400,另一个人B从2100工作到2200,还有一个人C从2101工作到2300。A的工作时间包括了B和C的,而我们在分离BC时所置的9导致了这样的情况,类似1111111111911111111111,而实际上这一整条都是A的连续工作,不应该被中间断开。 我们的计数模块没法处理这个问题,所以不妨在上一次置完9后重新把一个人的工作时间的中间部分全部置为1,这样可以A里面的9会被覆盖掉。

还有一些需要考虑的地方,比如说结束后立即开始如何计算空闲时间,如何把中间没有空闲的情况正确地输出,解决这些问题就可以搞定这道题了。

package milkingCows;

import java.util.Scanner;

//工作区间设为1
//区分出结束后立刻开始新一轮的情况
//记录用数组  时间线 开始时间 结束时间
public class Main {
	final static int MAXN= 5010;
	final static int MAXT= 1000000;
	//状态1 0 9
	static public int[] timeLine= new int[MAXT];
	//标记某时间是开始还是结束
	static public boolean[] staTime= new boolean[MAXT];
	static public boolean[] endTime= new boolean[MAXT];
	static public int n;
	static public int earlyStart= MAXT+10;
	static public int lateEnd= 0;
	//用来把一个人的工作区间置1
	static public int[] staMemo= new int[MAXN];
	static public int[] endMemo= new int[MAXN];
	
	public static void main(String[] args) {

		//输入数据
		Scanner console= new Scanner(System.in);
		n= console.nextInt();
		int sta, end;
		
		for(int i=0; i<n; i++) {
			sta= console.nextInt();
			end= console.nextInt();
			//得到时间范围
			if(sta<earlyStart) earlyStart= sta;
			if(end>lateEnd) lateEnd= end;
			
			staTime[sta]= true;
			endTime[end]= true;
			staMemo[i]= sta;
			endMemo[i]= end;
			
			
			///工作区间设为1
			for(int j=sta; j<=end; j++) {
				timeLine[j]= 1;
			}
		}
		
		//将结束后立刻开始的情况开头标注9
		for(int i=earlyStart; i<=lateEnd; i++) {
			if(endTime[i]==true&&staTime[i+1]== true)timeLine[i+1]=9;
		}
		
		//防止中间区间被注9
		for(int i=0; i<n; i++) {
			for(int j=staMemo[i]+1; j<=endMemo[i]; j++) {
				timeLine[j]= 1;
			}
		}
		
		
		
		//计算连续长度
		int maxBusy=0, maxFree= 0;
		boolean everFree= false;//用于标记有没有中间断开过 处理输出maxFree导致的未断开也为1的情况
		for(int i=earlyStart; i<=lateEnd;i++) {
			int j= i;
			int tempLen=0;
			switch(timeLine[i]) {
				case 1:
					while(timeLine[j]==1)j++;
					tempLen= j-i;
					if(tempLen> maxBusy)maxBusy= tempLen;
					i= j-1;//跨过为1的区域,回退1位因为for循环还会再+1;
					break;
				case 9:
					everFree= true;
					//处理工作
					j= i+1;//不可能只有9后面没有1,所以可以把i放在9后面
					while(timeLine[j]==1)j++;
					tempLen= j-i;
					if(tempLen> maxBusy)maxBusy= tempLen;
					i= j-1;
					break;
				case 0:
					everFree= true;
					while(timeLine[j]==0&&j<lateEnd)j++;
					tempLen= j-i;
					if(tempLen> maxFree)maxFree= tempLen;
					i= j-1;
					break;
				default:
					System.out.println("ERROR");
					
			}
		}
		
		if(everFree== false) maxFree= -1;//处理未曾空闲的情况
		System.out.println((maxBusy-1) +" "+ (maxFree+1));
		
		
		for(int j=earlyStart; j<=lateEnd; j++) {
			System.out.print(timeLine[j]);
		}
	}
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值