QUST程序设计赛G题:占教室

题目描述:

在青科大,经常有活动需要占用教室。。。。比如开班会。。比如办活动。。比如学长讲课。。。。。。。反正很多事啦。。。。于是。。。新一轮抢占教室开始了。。此前经常会有人占用教室时发生冲突(没有教室可占或者占不够需要的教室T T),为了社会的和谐我们决定成立协调委员会来协调教室帮助需要教室确无法满足的同学,我们有接下来n天的借教室信息,其中第i天学校有ri个教室可供租借。共有m组同学需要借用教室,每组用三个正整数描述,分别为dj,sj,tj,表示某租借者需要从第sj天到第tj天租 借教室(包括第sj天和第tj天),每天需要租借dj个教室。我们假定,租借的同学对教室的大小、地点没有要求。只需要满足每天dj个教室的供应即可,而它们具体是哪些教室,每天是否是相同的教室则没有意见。

借教室的原则自然是先到先得,(这m组人的排列次序就是来占教室的先后次序,先到的会在黑板上写上如“某某日至某某日占用教室。。请勿重占。。谢谢。。”等字样来确保占用的教室在需要的时候可用以保证自己活动的顺利进行。。。,如果时间不冲突的话教室可以共享(如编号1~k天a组占用,k+1~n天b组占用不会引起冲突)如果遇到一组人需要教室而无法完全满足,此时就是协调委员会派上用场的时候啦~这组人会跑去协调委员会寻求帮助。这里的无法满足指从

第sj天到第tj天中有至少一天剩余的教室数量不足dj个。 现在我们需要知道,是否会有人需要教室而无法完全满足。是哪一组去协调委员会寻求帮助。 

输入:

第一行有一个整数T表示测试数据的组数

第二行包含两个正整数n,m,表示天数和需要教室的同学的组数。 第三行包含n个正整数,其中第i个数为ri,表示第i天可用于租借的教室数量。 接下来有m行,每行包含三个正整数dj,sj,tj,表示租借的数量,占用开始、结束分别在第几天。 每行相邻的两个数之间均用一个空格隔开。天数与同学均用从1开始的整数编号。

数据范围:1 ≤ n,m ≤ 10^6,0 ≤ri,dj ≤ 10^9,1 ≤ sj ≤ tj ≤ n。

 输出:

有T组,每一组数据都以”Case #t:”t为当前数据组数(注全为半角符号)作为第一行接下来如果所有订单均可满足,则接下来的输出只有一行,包含一个整数 0。否则(订单无法完全满足) 接下来输出两行,第二行输出一个负整数-1,第三行输出需要帮助的同学的编号。 

样例输入:

1

4 3

2 5 4 3

2 1 3

3 2 4

4 2 4

样例输出:

Case #1:

-1

Tips:

第 1 组同学满足后,4 天剩余的教室数分别为 0,3,2,3。第 2 组同学要求第 2 天到 第 4 天每天提供 3 个教室,而第 3 天剩余的教室数为 2,因此无法满足。分配停止,第二组同学会去委员会寻求帮助。

 

题目解析:

看到题目的第一反应是模拟,队友的想法和我一样,于是很干脆的敲了一个模拟,结果自然而然的——没过……

这道题不能简单的使用模拟法做,因为数据太大会超时。

借教室的原则是先到先得,也就是说我们要按照订单的先后顺序依次为每份订单分配教室(这句话很重要,这决定了分配是按照顺序的)。如果在分配的过程中遇到一份订单无法完全满足,则需要停止教室的分配,通知当前申请人修改订单。这里的无法满足指从第sj天到第tj天中有至少一天剩余的教室数量不足dj个。

现在我们需要知道,是否会有订单无法完全满足。如果有,需要通知哪一个申请人修改订单。

         二分法代码如下:

#include<cstdio>
#include<cstring>
int r1[1000010],sum[1000010],d[1000010],s[1000010],t[1000010];
int main()
{
//	int r1[100010],sum[100010],d[100010],s[100010],t[100010];
//	int r1[1000010],sum[1000100],d[1000010],s[1000010],t[1000010];
	int n,m,i,s1,l,r,mid,now;
	int flag;
	int T;
	scanf("%d",&T);
//	printf("T %d\n",T);
	flag=0;
	for(int j=1;j<=T;j++)
	{
		printf("Case #%d:\n",T);
	//	memset(r1,0,sizeof(r1));
	//	memset(sum,0,sizeof(sum));
	//	memset(d,0,sizeof(d));
	//	memset(s,0,sizeof(s));
	//	memset(t,0,sizeof(t));
		scanf("%d %d",&n,&m);
	//	printf("n m %d %d\n",n,m);
		for(i=1;i<=n;i++)
		{
			scanf("%d",&r1[i]);
		//	printf("r%d %d\n",i,r1[i]);
		}
		for(i=1;i<=m;i++)
		{
			scanf("%d %d %d\n",&d[i],&s[i],&t[i]);
		//	printf("i d s t %d %d %d %d\n",i,d[i],s[i],t[i]);
		}
		l=1;
		r=m+1;	//二分终点要注意,如果最后终点不变,直到m+1才不合格,那么借用要求全部满足;如果r改变了,那么要求不满足
		now=0;//如果可行一定是连续的订单,所以可用二分 now指的是上一个mid,为了恢复sum数组 
		do
		{
			mid=((l+r)>>1);
		//	printf("mid now %d %d\n",mid,now);
			if(mid>now)
			{
				for(i=now+1;i<=mid;i++)
				{
					sum[s[i]]+=d[i];
					sum[t[i]+1]-=d[i];
				//	printf("SUM %d %d\n",sum[s[i]],sum[t[i]+1]);
				}
			}
			else
			{
				for(i=mid+1;i<=now;i++)
				{
					sum[s[i]]-=d[i];
					sum[t[i]+1]+=d[i];
				//	printf("SUM %d %d\n",sum[s[i]],sum[t[i]+1]);
				}
			}
			now=mid;
			flag=1;
			s1=0;
			for(i=1;i<=n;i++)//sum[]按照天数计
			{
				s1=s1+sum[i];
				if(s1>r1[i])
				{
					flag=0;
					break;
				}
			}
			if(flag==1)
			{
				l=mid+1;
			//	printf("l r %d %d\n",l,r);
			}
			else
				r=mid;
		}
		while(l!=r);
		if(flag==1&&l==m+1)
		{
			printf("0\n");
		}
		else
		{
			printf("-1\n");
			printf("%d\n",r);
		}
	}
	return 0;
}

另外山科大队用线段树解的,分析一下复杂度也是在可以接受的范围内的,线段树法代码如下:

#include <iostream>  
#include <cstdio>  
#include <cstdlib>  
#include <cstring>  
  
using namespace std;  
const int maxn = 1000000+10;  
  
int a[maxn];  
int n,m,s,t,need;  
struct node  
{  
       int Min[maxn*4];  
       int dec[maxn*4];  
       void   build(int a[],int l,int r,int p)  
       {  
              dec[p]=0;  
              if (l==r)  
              {  
                  Min[p] = a[l];  
                  return ;  
              }  
              int mid = (l+r) / 2;  
              build(a,l,mid,p+p);   
              build(a,mid+1,r,p+p+1);  
              Min[p] = min(Min[p+p],Min[p+p+1]);    
       }  
       void   update(int p )  
       {  
              Min[p] = min(Min[p+p]-dec[p+p],Min[p]);  
              Min[p] = min(Min[p+p+1]-dec[p+p+1],Min[p]);  
       }  
       void   push(int p)  
       {  
              dec[p+p]+=dec[p];  
              dec[p+p+1]+=dec[p];  
              dec[p]=0;  
       }  
       void   getDec(int l ,int r,int p, int a,int b,int need)  
       {  
              if (l==a && r==b)  
              {  
                  Min[p]-= (need+dec[p]);  
                  if (l!=r) dec[p+p]+=dec[p]+need,dec[p+p+1]+=dec[p]+need;  
                  dec[p]=0;  
                  return ;  
              }  
              push(p);  
              int mid = (l+r) / 2;  
              if (b <= mid ) getDec(l,mid,p+p,a,b,need); else  
              if (a >  mid ) getDec(mid+1,r,p+p+1,a,b,need); else  
              {  
                 getDec(l, mid ,p+p,a,mid,need);  
                 getDec(mid+1,r,p+p+1,mid+1,b,need);     
              }   
              update(p);  
       }  
         
}tree;  
int main()  
{  
    cin >> n >> m ;  
    for (int i = 1; i <= n ;++i ) scanf("%d",&a[i]);  
    tree.build(a,1,n,1);  
      
    for (int i = 1; i <= m ;++i)  
    {  
        scanf("%d %d %d",&need, &s,&t);  
        tree.getDec(1,n,1,s,t,need);  
        if (tree.Min[1] < 0 )  
        {  
            printf("%d\n%d\n",-1,i);  
            return 0;  
        }  
    }  
    cout << 0 << endl;  
    return 0;  
}  

*注:本题改编自NOIP2012提高组  借教室



评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值