(二分、分治、贪心 ) 北大ACM/ICPC暑期竞赛训练

目录

二分

01:派

02:河中跳房子

03:矩形分割

分治

07:求排列的逆序数

基于归并排序

08:输出前k大的数

基于快速排序

贪心

百炼 4110:圣诞老人的礼物-Santa Clau’s Gifts

百炼 4151:电影节

POJ 3190 Stall Reservations


 

二分

01:派

描述

我的生日要到了!根据习俗,我需要将一些派分给大家。我有N个不同口味、不同大小的派。有F个朋友会来参加我的派对,每个人会拿到一块派(必须一个派的一块,不能由几个派的小块拼成;可以是一整个派)。

我的朋友们都特别小气,如果有人拿到更大的一块,就会开始抱怨。因此所有人拿到的派是同样大小的(但不需要是同样形状的),虽然这样有些派会被浪费,但总比搞砸整个派对好。当然,我也要给自己留一块,而这一块也要和其他人的同样大小。

请问我们每个人拿到的派最大是多少?每个派都是一个高为1,半径不等的圆柱体。

输入

第一行包含两个正整数N和F,1 ≤ N, F ≤ 10 000,表示派的数量和朋友的数量。
第二行包含N个1到10000之间的整数,表示每个派的半径。

输出

输出每个人能得到的最大的派的体积,精确到小数点后三位。

样例输入

3 3
4 3 3

样例输出

25.133

 

题意:给定派的数目和朋友的数目,把给定的这些派一定要分割成大小相同然后分给朋友,同时还要留给自己一个
所以分派的人数要是朋友人数加1,求最大化的最小体积,

首先先求派的体积,最小分派是0,L=0,最大分派就是R=a[n-1],
利用二分求派的体积,然后判断这些派是否能够分给这些人。

#include<stdio.h>
#include<stdlib.h>
#include<iostream>
#include<algorithm>
#include<string.h>
#include<math.h>
using namespace std;
double pi=acos(-1.0);//圆周率的计算公式  
int n,f;
double a[10010];
bool check(double v)
{
	int cnt=0;
	for(int i=0;i<n;i++)
	{
		cnt=cnt+a[i]/v;
	}
	if(cnt>=f+1)
	 return true;
	else
	 return false;
}
int main()
{
	scanf("%d%d",&n,&f);
	int ans;
	for(int i=0;i<n;i++)
	{
		scanf("%lf",&a[i]);
	}
	sort(a,a+n);
	for(int i=0;i<n;i++)
	{
		a[i]=a[i]*a[i]*pi;
	}
	double L=0,R=a[n-1];
	while(R-L>=1e-6)  //精度易错 
	{
		double mid=L+(R-L)/2;
		if(check(mid))
		{
           L=mid;
		}
		else
		{
			R=mid;
		}
	}
	printf("%.3lf\n",L);
	return 0;
}

注意:

1.π的取值公式

2.二分循环的精度

3.要学会怎样二分,把什么二分,利用二分法求答案目前要弄清这两个问题。

4.做法比较独特,有许多地方需要借鉴 https://www.cnblogs.com/zzyh/p/6638583.html

 

02:河中跳房子

描述

每年奶牛们都要举办各种特殊版本的跳房子比赛,包括在河里从一个岩石跳到另一个岩石。这项激动人心的活动在一条长长的笔直河道中进行,在起点和离起点L远 (1 ≤ L≤ 1,000,000,000) 的终点处均有一个岩石。在起点和终点之间,有N (0 ≤ N ≤ 50,000) 个岩石,每个岩石与起点的距离分别为Di (0 < Di < L)。

在比赛过程中,奶牛轮流从起点出发,尝试到达终点,每一步只能从一个岩石跳到另一个岩石。当然,实力不济的奶牛是没有办法完成目标的。

农夫约翰为他的奶牛们感到自豪并且年年都观看了这项比赛。但随着时间的推移,看着其他农夫的胆小奶牛们在相距很近的岩石之间缓慢前行,他感到非常厌烦。他计划移走一些岩石,使得从起点到终点的过程中,最短的跳跃距离最长。他可以移走除起点和终点外的至多(0 ≤ M ≤ N) 个岩石。

请帮助约翰确定移走这些岩石后,最长可能的最短跳跃距离是多少?

 

输入

第一行包含三个整数L, N, M,相邻两个整数之间用单个空格隔开。
接下来N行,每行一个整数,表示每个岩石与起点的距离。岩石按与起点距离从近到远给出,且不会有两个岩石出现在同一个位置。

输出

一个整数,最长可能的最短跳跃距离。

样例输入

25 5 2
2
11
14
17
21

样例输出

4

提示

在移除位于2和14的两个岩石之后,最短跳跃距离为4(从17到21或从21到25)。

题意:移石头,两个岩石之间的距离为D,从一个岩石出发跳到另一个岩石,
找最大化的最短距离。

从一个石头i开始,跳到岩石j,i到j之间距离如果大于mid,则把i与j之间的(j-i-1)个石头全部移走
否则,j继续++

如果移走的石头小于等于m,证明mid太小了,需要大一点的mid

如果移走的石头大于m,证明mid太大了,需要小一点的mid

#include<stdio.h>
#include<stdlib.h>
#include<iostream>
#include<algorithm>
#include<string.h>
#include<math.h>
using namespace std;
int l,n,m;
int a[50010];
int main()
{
	scanf("%d%d%d",&l,&n,&m);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
	}
	a[0]=0;
	a[n+1]=l;
	int L=0,R=l+1;
	while(R-L>1)
	{
		int mid=L+(R-L)/2;
		int p=0;
		int i=0;
		while(i<=n)
		{
			int j=i+1;
			while(j<=n+1 && a[j]-a[i]<mid)
			{
				j++;
			}
			p+=j-i-1;
			i=j;
		}
		   if(p<=m)
			{
				L=mid;
			//	cout<<"L="<<L<<endl;
			}
			else
			{
				R=mid;
			//	cout<<"R="<<R<<endl;
			}
	}
	printf("%d\n",L);
	return 0;
}

注意:

1. i与j之间移动的石头的个数为j-i-1;

 

https://blog.csdn.net/LMengi000/article/details/81451748  好斗的奶牛变形做法

#include<stdio.h>
#include<stdlib.h>
#include<iostream>
#include<algorithm>
#include<string.h>
#include<math.h>
using namespace std;
int l,n,m;
int a[50010];
bool check(int d)
{
    int cnt=1;
    int preston=0;
    for(int i=1;i<=n+1;i++)
    {
    	if(a[i]-a[preston]>=d)
    	{
    		cnt++;
    		preston=i;
		}
		if(cnt>=n+2-m)
		 return true;
	}
	return false;
}
int main()
{
	int ans;
  scanf("%d%d%d",&l,&n,&m);
  for(int i=1;i<=n;i++)
  {
  	scanf("%d",&a[i]);
  }
  a[0]=0;
  a[n+1]=l;
  int L=0,R=l;
  while(L<=R)
  {
  	int mid=L+(R-L)/2;
  	//cout<<"mid="<<mid<<endl;
  	if(check(mid))
  	{
  		ans=mid;
       L=mid+1;	
	   //cout<<"L="<<L<<endl;	
    }
    else
    {
    	R=mid-1;
    	//cout<<"R="<<R<<endl;
	}
  }
  printf("%d\n",ans);
  return 0;
} 
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int maxn=5e4+10;
const int inf=1e9;
int L,N,M,D[maxn];

bool C(int d){
	int last,crt;
	for(int i=0;i<M;i++){
		if(i==0){
			crt=0;
			while(crt<N&&D[crt]<d)
				crt++;
		}else{
			crt=last+1;
			while(crt<N&&D[crt]-D[last]<d)
				crt++;
		}
		if(crt==N) return false;
		last=crt;
	}
	while(crt<N&&L-D[last]<d)
		crt++;
	if(crt==N) return false;
	return true;
}

void solve(){
	sort(D,D+N);
	int lb=1,ub=inf;
	while(ub-lb>1){
		int mid=(lb+ub)/2;
		if(C(mid)) lb=mid;
		else ub=mid;
	}
	printf("%d\n",lb);
}

int main(){
	scanf("%d%d%d",&L,&N,&M);
	for(int i=0;i<N;i++)
		scanf("%d",&D[i]);
	M=N-M;
	solve();
	return 0;
}

03:矩形分割

描述

平面上有一个大矩形,其左下角坐标(0,0),右上角坐标(R,R)。大矩形内部包含一些小矩形,小矩形都平行于坐标轴且互不重叠。所有矩形的顶点都是整点。要求画一根平行于y轴的直线x=k(k是整数) ,使得这些小矩形落在直线左边的面积必须大于等于落在右边的面积,且两边面积之差最小。并且,要使得大矩形在直线左边的的面积尽可能大。注意:若直线穿过一个小矩形,将会把它切成两个部分,分属左右两侧。

输入

第一行是整数R,表示大矩形的右上角坐标是(R,R) (1 <= R <= 1,000,000)。
接下来的一行是整数N,表示一共有N个小矩形(0 < N <= 10000)。
再接下来有N 行。每行有4个整数,L,T, W 和 H, 表示有一个小矩形的左上角坐标是(L,T),宽度是W,高度是H (0<=L,T <= R, 0 < W,H <= R). 小矩形不会有位于大矩形之外的部分。

输出

输出整数n,表示答案应该是直线 x=n。 如果必要的话,x=R也可以是答案。

样例输入

1000
2
1 1 2 1
5 1 2 1

样例输出

5

题意:把坐标轴从0到R二分,当二分到某一个x值时,使得1.左边小矩形的面积大于等于右边小矩形的面积,且且两边面积之差最小,这一步在二分完成之后,就已经完成了。2.使得大矩形在直线左边的的面积尽可能大,二分完成之后,最优解有有left和right,目前自己有一个疑惑,就是在取最优解保证大矩形在直线左边的面积尽可能大,之前自己做的时候,直接采用了差的形式,但是直接做差比较会很不准确,想了好久总是走不出这个误区。

自己的想法和网上这份代码相似度95%,但是依旧不能理解它选择最优解的过程,用绝对值能保证左边小矩形的面积大于等于右边小矩形的面积,且且两边面积之差最小,但是不能使得大矩形在直线左边的的面积尽可能大

相似度极高代码::https://blog.csdn.net/zhhe0101/article/details/52794852  

#include<stdio.h>
#include<stdlib.h>
#include<iostream>
#include<algorithm>
#include<string.h>
#include<math.h>
#include<queue>
#include<vector>
using namespace std;
long long int R,n;
long long int sum1,sum2;
long long int maxn,ans;
struct rect
{
	long long int L,T,W,H;//分别是小矩形的左上端点 宽  高
	long long int area;
	long long int y;
}a[500100];
long long int S(int mid)
{
	sum1=sum2=0;
	for(int i=0;i<n;i++)
	{
		if(a[i].y<=mid)
		{
			sum1+=a[i].area;
		}else if(a[i].L<mid && a[i].y>mid )
		{
			sum1+=(mid-a[i].L)*a[i].H;
			sum2+=(a[i].y-mid)*a[i].H;
		}
		else
		{
			sum2+=a[i].area;
		}
	}
	return sum1-sum2;
}
int main()
{
	scanf("%d",&R);
	scanf("%d",&n);
	long long int maxn=-1;
	for(int i=0;i<n;i++)
	{
		scanf("%lld%lld%lld%lld",&a[i].L,&a[i].T,&a[i].W,&a[i].H);
		 a[i].area=a[i].W*a[i].H;//小矩形的面积 
		 a[i].y=a[i].L+a[i].W;//小矩形最右端的端点横坐标
		 if(a[i].L+a[i].W>maxn)
		  maxn=a[i].L+a[i].W;
	}
	int left=0,right=R;
	while(right-left>1)
	{
		int mid=left+(right-left)/2;
		if(S(mid)<=0)//使得这些小矩形落在直线左边的面积必须大于等于落在右边的面积,且两边面积之差最小。这样就会出现left边界和right边界
		{
			left=mid;
		}
		else
		{
			right=mid;
		}
	}
//使得大矩形在直线左边的的面积尽可能大 
	long long int p1=S(left);//以left为分界线,左右的面积之差
	long long int p2=S(right);//以right为分界线,左右的面积之差
	if(abs(p1)>=abs(p2))
	{
		ans=right;
	}
	else if(abs(p1)<abs(p2))
	{
		ans=left;
	}
	if(ans==maxn)
	{
		ans=R;
	}
/*
    	if(p1<p2)
	{
		if(p1>=0)
		 ans=left;
		else
	     ans=right;	 
	}else if(p1>p2)
	{
		if(p2>=0)
		 ans=right;
		else
		 ans=left;
	}
	if(ans==maxn)
	{
		ans=R;
	}
*/
	printf("%lld\n",ans);
	return 0;
}

 

分治

07:求排列的逆序数

描述

在Internet上的搜索引擎经常需要对信息进行比较,比如可以通过某个人对一些事物的排名来估计他(或她)对各种不同信息的兴趣,从而实现个性化的服务。

对于不同的排名结果可以用逆序来评价它们之间的差异。考虑1,2,…,n的排列i1,i2,…,in,如果其中存在j,k,满足 j < k 且 ij > ik, 那么就称(ij,ik)是这个排列的一个逆序。

一个排列含有逆序的个数称为这个排列的逆序数。例如排列 263451 含有8个逆序(2,1),(6,3),(6,4),(6,5),(6,1),(3,1),(4,1),(5,1),因此该排列的逆序数就是8。显然,由1,2,…,n 构成的所有n!个排列中,最小的逆序数是0,对应的排列就是1,2,…,n;最大的逆序数是n(n-1)/2,对应的排列就是n,(n-1),…,2,1。逆序数越大的排列与原始排列的差异度就越大。

现给定1,2,…,n的一个排列,求它的逆序数。
 

输入

第一行是一个整数n,表示该排列有n个数(n <= 100000)。
第二行是n个不同的正整数,之间以空格隔开,表示该排列。

输出

输出该排列的逆序数。

样例输入

6
2 6 3 4 5 1

样例输出

8

提示

1. 利用二分归并排序算法(分治);
2. 注意结果可能超过int的范围,需要用long long存储。

基于归并排序

利用分治思想,来求逆序数
样例 2 6 3 4 5 1
1.利用二分,把数组分成两份,左边的逆序数+右边的逆序数
2.左边取一个数,右边取一个数,这样组成的逆序数相加
2 6 3 4 5 1 分成两组 
一 2 6 3;又二分成 2 6 ;又二分成 2; 左边数组又分成两份 
数组一:<2>  <2,6>  <2,6,3>
数组二:<6>  <3>
1.2<6,升序,不是逆序数 ; 
2.2<3,升序,不是逆序数; 6>3,降序,是逆序数,并且需要排序,
排完顺序之后,是 2 3 6  逆序数是1 
二 4 5 1;又二分成 4 5 ;又二分成 4;  右边数组又分成两份 
数组一:<4> <4,5> <1,4,5>
数组二:<5> <1>
1.4<5,升序,不是逆序数 ;
2.4>1,降序,是逆序数,并且需要排序;
  5>1,降序,是逆序数,并且需要排序;
3.排完顺序之后,是 1 4 5  逆序数是2
上面过程之后 数组一 2 3 6 数组二 1 4 5 再综合排序
                   p1   m       p2
2>1,所以2之后的数据都能与1构成逆序数 <2,1><3,1><6,1>
6>4,所以6之后(6是最后一个元素)的数据都能与1构成逆序数 <6,4>
6>5,所以6之后(6是最后一个元素)的数据都能与1构成逆序数 <6,5>

#include<stdio.h>
#include<stdlib.h>
#include<iostream>
#include<algorithm>
#include<string.h>
#include<math.h>
using namespace std;
const long long int maxn=1e5+10;
long long  a[maxn];
long long  b[maxn];
int n;
long long int cnt;
void Merge(long long int a[],int s,int m,int e,long long int tmp[])
{
	int pb=0;
	int p1=s,p2=m+1;
	while(p1<=m && p2<=e)
	{
		 if(a[p1]<a[p2])
		 {
		 	tmp[pb++]=a[p1++];

		 }
		 else
		 {
		 	cnt=cnt+(m-p1+1);
		 	tmp[pb++]=a[p2++];
		 }
	}
	while(p1<=m)
	{
		tmp[pb++]=a[p1++];
	}
	while(p2<=e)
	{
		tmp[pb++]=a[p2++];
	}
	for(int i=0;i<e-s+1;i++)
	   a[s+i]=tmp[i];
}
void MergeSort(long long int a[],int s,int e,long long int tmp[])
{
	if(s<e)
	{
		int m=s+(e-s)/2;
		MergeSort(a,s,m,tmp);
		MergeSort(a,m+1,e,tmp);
		Merge(a,s,m,e,tmp);
	}
}
int main()
{
	scanf("%d",&n);
	cnt=0;
	for(int i=0;i<n;i++)
	{
		scanf("%d",&a[i]);
	}
	MergeSort(a,0,n-1,b);
	printf("%lld\n",cnt);
	return 0;
}

注意:

1.要注意细节,知道数组起始和结束,这之间元素的个数是 结束-起始+1;

2.分治的思想是要把所求的问题分块,一块一块来求,然后再归并到整个,所以一开始分块的时候,是要把每一块都分到最小的。

3.cnt的个数是(e-s+1)个,这个要注意+1操作

08:输出前k大的数

描述

给定一个数组,统计前k大的数并且把这k个数从大到小输出。

输入

第一行包含一个整数n,表示数组的大小。n < 100000。
第二行包含n个整数,表示数组的元素,整数之间以一个空格分开。每个整数的绝对值不超过100000000。
第三行包含一个整数k。k < n。

输出

从大到小输出前k大的数,每个数一行。

样例输入

10
4 5 6 9 8 7 1 2 3 0
5

样例输出

9
8
7
6
5

基于快速排序

本题目要求用分治法做这道题目,目前没有用排序时排最少个数的方法把这个题目做出来,总是会出现死循环

AC代码,但没有体现分治----减少排序元素的个数

#include<stdio.h>
#include<stdlib.h>
#include<iostream>
#include<algorithm>
#include<string.h>
#include<math.h>
using namespace std;
const int maxn=1e5+10;
int n;
int a[maxn];
int k;
void swap(int &a,int &b)
{
	int temp=a;
	a=b;
	b=temp;
}
/*
void QuickSort(int a[],int s,int e,int k)
{
	if(s>=e)
	 return ;
	int p=a[s];
	int i=s,j=e;
	while(i!=j)
	{
		while(i<j && a[j]>=p)
		  --j;
		swap(a[i],a[j]);
		while(i<j && a[i]<=p)
		  ++i;
		swap(a[i],a[j]);
	}
	int num=e-i+1;
	if(num==k)
	 return ;
	if(num>k)
	 QuickSort(a,i+1,e,k);
	if(num<k)
	 QuickSort(a,s,i-1,k-num);
}*/
void arrangeRight(int *a,int s,int e,int k)
{	if(s>=e)
	return ;
	int t=a[s];
	int i=s,j=e;
    while(i!=j){	
		while(i<j&&t<=a[j])//不要漏掉等号
			--j;
		swap(a[i],a[j]);
		while(i<j&&t>=a[i])	
		    ++i;
	    swap(a[i],a[j]);
			}
    arrangeRight(a,s,i-1,k);
    arrangeRight(a,i+1,e,k);
}
int main()
{
	scanf("%d",&n);
	for(int i=0;i<n;i++)
	{
		scanf("%d",&a[i]);
	}
	scanf("%d",&k);
    arrangeRight(a,0,n-1,k);
   /* for(int i=0;i<n;i++)
    {
		printf("%d\n",a[i]);	
	}*/
	for(int i=n-1;k>0;i--)
    {
		printf("%d\n",a[i]);
		k--;
	}
	return 0;
}

注意:

1.再排序的时,函数arrangeRight里面,判断--j和++i时,while循环条件里面等号不要漏掉

2.排序的起始位置和终止位置,快速排序与归并排序区分开

贪心

百炼 4110:圣诞老人的礼物-Santa Clau’s Gifts

 

描述

圣诞节来临了,在城市A中圣诞老人准备分发糖果,现在有多箱不同的糖果,每箱糖果有自己的价值和重量,每箱糖果都可以拆分成任意散装组合带走。圣诞老人的驯鹿最多只能承受一定重量的糖果,请问圣诞老人最多能带走多大价值的糖果。

输入

第一行由两个部分组成,分别为糖果箱数正整数n(1 <= n <= 100),驯鹿能承受的最大重量正整数w(0 < w < 10000),两个数用空格隔开。其余n行每行对应一箱糖果,由两部分组成,分别为一箱糖果的价值正整数v和重量正整数w,中间用空格隔开。

输出

输出圣诞老人能带走的糖果的最大总价值,保留1位小数。输出为一行,以换行符结束。

样例输入

4 15
100 4
412 8
266 7
591 2

样例输出

1193.0

题意:雪橇承载的重量有限,要保证承载的价值最大,就要求出,在同等重量下,谁的价值大就要先把谁装进去,所以就要求出价值/重量这一比值,按照从大到小的顺序排序。但是当雪橇承载的重量不足以再次承载整箱糖果时,就要把一箱糖果拆开,能放发多少重量就放多少重量,然后再算出放剩下这些重量的价值。

#include<stdio.h>
#include<stdlib.h>
#include<iostream>
#include<algorithm>
#include<string.h>
#include<math.h>
using namespace std;
const int maxn=1e4+10;
int n,w;
struct gift
{
	int v;
	int w;
	double ave;
}candies[maxn];
int compare(struct gift a,struct gift b)
{
	return a.ave>b.ave;
}
int main()
{
	scanf("%d%d",&n,&w);
	for(int i=0;i<n;i++)
	{
		scanf("%d%d",&candies[i].v,&candies[i].w);
		candies[i].ave=(double)candies[i].v/(double)candies[i].w;
	}
	sort(candies,candies+n,compare);
	int totalw=0;
	double totalv=0;
	for(int i=0;i<n;i++)
	{
		if(totalw+candies[i].w<=w)
		{
			totalw+=candies[i].w;
			totalv+=candies[i].v;
		}
		else//需要注意
		{
			totalv+=candies[i].v*double(w-totalw)/candies[i].w;
			break;
		}
	}
	printf("%.1lf\n",totalv);
	return 0;
}

注意:

1.当不足以放下正像糖果的时候,要把糖果拆开,装入剩余的重量,使得雪橇承载的重量等于雪橇能够承载的重量,然后还要计算装入这些糖果的价值。

百炼 4151:电影节

描述

大学生电影节在北大举办! 这天,在北大各地放了多部电影,给定每部电影的放映时间区间,区间重叠的电影不可能同时看(端点可以重合),问李雷最多可以看多少部电影。

输入

多组数据。每组数据开头是n(n<=100),表示共n场电影。
接下来n行,每行两个整数(0到1000之间),表示一场电影的放映区间
n=0则数据结束

输出

对每组数据输出最多能看几部电影

样例输入

8
3 4
0 7 
3 8 
15 19
15 20
10 15
8 18 
6 12 
0

样例输出

3

来源

Guo Wei

题意:贪心的解法,可以自己出几组数据试验一下, 如果要保证看的电影最多的话,就要按照电影结束的时间最早,然后再接着看下一场电影,这样就能保证看的电影的次数最多,所以要对电影的结束时间从小到大排序,假设第一场电影已经看过了,然后从第一场电影的结束时间往后继续找符合条件的电影。

#include<stdio.h>
#include<stdlib.h>
#include<iostream>
#include<algorithm>
#include<string.h>
#include<math.h>
using namespace std;
int n,cnt=0;
struct film
{
	int s;
	int e;
}film[1100];
int compare(struct film a,struct film b)
{
	return a.e<b.e;
}
int main()
{
  while(1)
  {
   	scanf("%d",&n);
     if(n==0)
       break;
   for(int i=0;i<n;i++)
   {
    scanf("%d%d",&film[i].s,&film[i].e);	
   }	
  sort(film,film+n,compare);
  cnt=1;
  int last=0;
  for(int i=1;i<n;i++)
  {
    if(film[i].s>=film[last].e)
    {
    	cnt++;
    	last=i;
	}
  }
  printf("%d\n",cnt);
  }
return 0;
}

注意:

1. 用last变量来记录当前电影,在找到合适电影的时候,就要更换last的值。

POJ 3190 Stall Reservations

Description

Oh those picky N (1 <= N <= 50,000) cows! They are so picky that each one will only be milked over some precise time interval A..B (1 <= A <= B <= 1,000,000), which includes both times A and B. Obviously, FJ must create a reservation system to determine which stall each cow can be assigned for her milking time. Of course, no cow will share such a private moment with other cows. 

Help FJ by determining:

  • The minimum number of stalls required in the barn so that each cow can have her private milking period
  • An assignment of cows to these stalls over time

Many answers are correct for each test dataset; a program will grade your answer.

Input

Line 1: A single integer, N 

Lines 2..N+1: Line i+1 describes cow i's milking interval with two space-separated integers.

Output

Line 1: The minimum number of stalls the barn must have. 

Lines 2..N+1: Line i+1 describes the stall to which cow i will be assigned for her milking period.

Sample Input

5
1 10
2 4
3 6
5 8
4 7

Sample Output

4
1
2
3
2
4

Hint

Explanation of the sample: 

Here's a graphical schedule for this output: 
 

Time     1  2  3  4  5  6  7  8  9 10

Stall 1 c1>>>>>>>>>>>>>>>>>>>>>>>>>>>

Stall 2 .. c2>>>>>> c4>>>>>>>>> .. ..

Stall 3 .. .. c3>>>>>>>>> .. .. .. ..

Stall 4 .. .. .. c5>>>>>>>>> .. .. ..

Other outputs using the same number of stalls are possible.

Source

USACO 2006 February Silver

题意:n头牛要挤奶,每头牛都有一个挤奶的时间区间,牛需要在牲畜栏里挤奶,一个牲畜栏只能容纳一头牛,问至少需要多少个牲畜栏,才能完成全部挤奶工作,以及每头牛都放在哪个牲畜栏里。

两头牛的挤奶时间区间哪怕是端点重合也是不可以的。

1.牛到了要挤奶的时间,就要去牲畜栏里去挤奶,因此牛的挤奶顺序是牛的开始挤奶时间决定的,谁开始的时间早,谁就先去挤奶,因此,牛的排序是按照牛的开始时间按照从小到大排的。

2.牲畜栏的使用时间是在不断变化的,牛挤奶结束,牲畜栏就使用完毕,就可以再给下一个要挤奶的奶牛使用;按照优先队列用法,将牲畜栏的使用结束时间按照从大到小排序,时间最早的优先级最高,放在队头。

3.再进行的过程就是奶牛选牲畜栏进行挤奶了,定义total来表示建立的牲畜栏的数目。

 (1)如果牲畜栏为空,则需要建立一个牲畜栏,用pos数组来记录编号为0的奶牛挤奶的牲畜栏的编号,将奶牛挤奶结束的时间和牲畜栏编号传给Stall,以便排序。

 (2)如果牲畜栏部位空,则奶牛挤奶就需要判断当前是否有牲畜栏可以使用,如果有牲畜栏可以使用,就直接取队头牲畜栏,将奶牛的挤奶的牲畜栏编号赋值给pos,再将奶牛的挤奶结束时间和牲畜栏编号进行排序。

(3)如果奶牛没有牲畜栏可以去挤奶,就需要新建一个牲畜栏,这样total就需要+1,记录奶牛挤奶的牲畜栏编号,然后再对奶牛的挤奶结束时间和牲畜栏编号进行排序。

#include<stdio.h>
#include<stdlib.h>
#include<iostream>
#include<algorithm>
#include<string.h>
#include<math.h>
#include<queue>
#include<vector>
using namespace std;
struct Cow
{
	int a,b;
	int No;
	bool operator<(const Cow & c) const
	{
	  return a<c.a;//
	}
}cows[50100];
int pos[50100];
struct Stall
{
	int end;
	int No;
	bool operator<(const Stall & s)const
	{
		return end>s.end;// 
	}
	Stall(int e,int n):end(e),No(n){ }
	/* 等价于 意义在于修改牲畜栏的结束时间和牲畜栏的编号 
	Stall s;
	s.end=cows[i].b;
	s.No=total; 
	*/
};
int main()
{
	int n;
	scanf("%d",&n);
	for(int i=0;i<n;i++)
	{
		scanf("%d%d",&cows[i].a,&cows[i].b);//
		cows[i].No=i;//
	}
	sort(cows,cows+n);
	int total = 0;  //表示新建的牲畜栏 
	priority_queue<Stall>pq; //优先队列处理牲畜栏, 
	for(int i=0;i<n;++i)
	{
		if(pq.empty())//表示没有牲畜栏,奶牛还没有被处理 
		{
			++total;//建立一个牲畜栏 
			pq.push(Stall(cows[i].b,total));//牲畜栏的结束时间和牲畜栏的编号 
			pos[cows[i].No]=total;//编号为No的奶牛进入了total这个牲畜栏里 
		}
		else
		{
			Stall st =pq.top();//取队头,结束时间最早的牲畜栏 
            if(st.end<cows[i].a)//找到空的牲畜栏 
			{
			  pq.pop();
			  pos[cows[i].No]=st.No;//记录奶牛放入的牲畜栏的编号 
			  pq.push(Stall(cows[i].b,st.No));//有新的奶牛放入,就要修改牲畜栏的结束时间,给牲畜栏重新编号。 
			} 
			else//没有找到空的牲畜栏,就要新建一个牲畜栏 
			{
				++total;//新建牲畜栏 
				pq.push(Stall(cows[i].b,total)); //修改牲畜栏的结束时间和编号 
				pos[cows[i].No]=total;//记录刚刚奶牛放入的牲畜栏的编号 
			}
		}
	}
	printf("%d\n",total);
	for(int i=0;i<n;++i)
	{
		printf("%d\n",pos[i]);
	}
return 0;
}

注意:

1.不要忘记对奶牛的编号进行赋初值 cows[i].No

2.不要忘记对奶牛的初始时间进行从小到大进行排序

3.要把奶牛挤奶结束的时间和牲畜栏的编号进行重新赋值

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值