LUOGU P1083 借教室

P1083 借教室
题目描述
在大学期间,经常需要租借教室。大到院系举办活动,小到学习小组自习讨论,都需要向学校申请借教室。教室的大小功能不同,借教室人的身份不同,借教室的手续也不一样。
面对海量租借教室的信息,我们自然希望编程解决这个问题。
我们需要处理接下来nn天的借教室信息,其中第i天学校有ri个教室可供租借。共有m份订单,每份订单用三个正整数描述,分别为dj,sj,tj,表示某租借者需要从第sj天到第tj天租借教室(包括第sj天和第tj天),每天需要租借dj个教室。
我们假定,租借者对教室的大小、地点没有要求。即对于每份订单,我们只需要每天提供dj个教室,而它们具体是哪些教室,每天是否是相同的教室则不用考虑。
借教室的原则是先到先得,也就是说我们要按照订单的先后顺序依次为每份订单分配教室。如果在分配的过程中遇到一份订单无法完全满足,则需要停止教室的分配,通知当前申请人修改订单。这里的无法满足指从第sj天到第tj天中有至少一天剩余的教室数量不足dj个。
现在我们需要知道,是否会有订单无法完全满足。如果有,需要通知哪一个申请人修改订单。
输入格式
第一行包含两个正整数n,m,表示天数和订单的数量。
第二行包含n个正整数,其中第i个数为ri,表示第i天可用于租借的教室数量。
接下来有m行,每行包含三个正整数dj,sj,tj,表示租借的数量,租借开始、结束分别在第几天。
每行相邻的两个数之间均用一个空格隔开。天数与订单均用从1开始的整数编号。
输出格式
如果所有订单均可满足,则输出只有一行,包含一个整数0。否则(订单无法完全满足)
输出两行,第一行输出一个负整数−1,第二行输出需要修改订单的申请人编号。
输入输出样例
输入 #1复制
4 3
2 5 4 3
2 1 3
3 2 4
4 2 4
输出 #1复制
-1
2
说明/提示
【输入输出样例说明】
第 1份订单满足后,4天剩余的教室数分别为 0,3,2,3。第 2 份订单要求第 2天到第 4 天每天提供3个教室,而第 3 天剩余的教室数为2,因此无法满足。分配停止,通知第2 个申请人修改订单。
【数据范围】
对于10%的数据,有1≤ n,m≤ 10;
对于30%的数据,有1≤ n,m≤1000;
对于 70%的数据,有1 ≤ n,m ≤ 10^5;
对于 100%的数据,有1 ≤ n,m ≤ 10^6,0 ≤ ri,dj ≤ 10 ^9,1 ≤ sj≤ tj≤ n。
NOIP 2012 提高组 第二天 第二题

题目分析
题意不难理解:给出n天每天有多少教室,给出m个人要在s到t天借d个教室,求是否会出现错误,若是求出编号
首先看到这一题,我所捕捉到的关键字便是计数,所以我的第一个想法是树状数组 (我太蒻了,没想到线段树,我也不会线段树,手动滑稽)
但是一般的求解复杂度可能会比较大,所以我们差分一下 (一点用没有),这样求一个具体天数剩的教室只需要getsum一次(差分过的数组里的数等于前面的加起来)
在一番经典操作后,心里美滋滋,感觉可以不难拿下,但是提交上去只有40分!
why?
对于每一个元素,我们如果使用差分的话,需要对当前的 s ~ n 以及 t+1 ~ n 进行操作,并且在最后的顺序判断时我们要频繁调用getsum来求和,不超时就怪了,所以不难理解会超时 (可能是我比较菜,可以优化多拿一些分)
但是最令人生气的是,写树状数组,40分,暴力的 O(N^2) 算法——45分!!太令人生气了,凭什么

所以,我们来考虑更完美的方法——差分+二分!!!

首先我们来说一说二分
这里我们可以用二分来枚举可能出问题的人的编号
对于一个合法的编号,可以说明,这个前面的没有问题,所以我们可以继续往后面找
对于不合法的编号,我们这需要向前面找,因为题目说述的是遇到一个不合法的就停止,故要找更前面的让我们GG的编号

对于原来的数组,我们可以进行拆分处理,这是为什么呢?
对于一个拆分数组,我们要改变区间的值,只需要 O(1) 的时间,证明如下:
对于一个已经差分后的数组 Ai
假设要修改 x 到 y 这一段区间的值,设改变的值为 c,按照本题题意,是需要减去的,那么
对于 Ax 则为 Ax-c
则 Ax+1 则为 Ax+1 - c - Ax
如果还原成之前的形式的话,则 Ax+1 - c - (Ax - c) = Ax+1 - c - Ax + c = Ax+1 所以,对于后面的数值是不会改变的
也就是,Ax+1 ~ Ay 这一段的值侍不会改变的
但是,对于 Ay+1 的值是要加上 c 的,但是对于 Ay+1 后面的元素,也就是 Ay+2 之流的值不需要改变
这是为什么呢?
对于 Ay+1 我们是要求还原性的,也就是下面所需要用到的,
所谓的还原性也就是从第一个的到当前的值的前缀和是相同于之前原本的值的,
对于 Ax ~ Ay 一段只有 Ax的值改变了,所以需要加上 c,以保证还原性
综上,我们需要做出的改变的便是:
Ax - cAy+1 + c

所以对于目前二分枚举到的 mid 只需要 mid 次
同时,对于一个差分数组中原本数据的还原,便是求第一个到当前的下标的前缀和
对于一串中的合法性的判断也只是 O(n)

这样我们可以解决问题
That’s All.

代码
40分树状数组

#include 
using namespace std;

long long c[1000009];
long long n,m;
long long d,s,t;
//经典操作
int lowbit(int T) {
	return T&(-T);
}

void add(int x,int k) {
	for (int i = x; i <= n; i+=lowbit(i)) c[i] += k;  
}

long long getsum(int x) {
	long long ans = 0;
	for (int i = x; i > 0; i-=lowbit(i)) ans += c[i];
	return ans;
}

int main() {
	scanf("%lld %lld",&n,&m);
	
	long long last = 0;
	for (int i = 1; i <= n; i++) {
		long long k; scanf("%lld",&k);
		add(i,k-last);   //差分
		last = k;
	}
	
	for (int i = 1; i <= m; i++) {
		scanf("%lld %lld %lld",&d,&s,&t);

		add(s,-d); add(t+1,d);   //差分所需要的改变操作
	
		for (int j = s; j <= t; j++)
			if (getsum(j) < 0) {
				printf("-1\n%lld",i);
				return 0;
			}
	}
	
	printf("0");
	
	return 0;
}

45分暴力 hh

#include 
using namespace std;

int s[1000009],t[1000009],d[1000009],r[1000009];
int n,m;

int main() {
	scanf("%d %d",&n,&m);
	for (int i = 1; i <= n; i++) scanf("%d",&r[i]);
	for (int i = 1; i <= m; i++) scanf("%d %d %d",&d[i],&s[i],&t[i]);
	
	for (int i = 1; i <= m; i++) {
		for (int j = s[i]; j <= t[i]; j++) {
			r[j] -= d[i];
			if (r[j] < 0) {
				printf("-1\n%d",i);
				return 0;
			}
		}
	}
	
	printf("0");
	
	return 0;	
}

满分的:差分+二分

#include 
using namespace std;

struct node {
	long long d;   //要多少教室
	long long s;   //开始日期
	long long t;    //结束日期
}l[1000009];

long long sum[1000009],r[1000009];
long long n,m;

bool check(int x) {
	long long last = 0;
	for (int i = 1; i <= n; i++) sum[i] = r[i] - last, last = r[i];   //差分
	for (int i = 1; i <= x; i++) sum[l[i].s] -= l[i].d, sum[l[i].t+1] += l[i].d;
	
	long long tot = 0;   //用来还原的前缀和
	for (int i = 1; i <= n; i++) {
		tot += sum[i];
		if (tot < 0) return false;
	}

	return true;
}

int main() {
	scanf("%d %d",&n,&m);
	for (int i = 1; i <= n; i++) scanf("%d",&r[i]);
	for (int i = 1; i <= m; i++) scanf("%d %d %d",&l[i].d,&l[i].s,&l[i].t);

	if (check(m)) {   //一列直接合法
		printf("0"); return 0;
	}

	long long l = 1, r = m;   //二分
	while (l < r) {
		long long mid = (l+r) / 2;
		if (check(mid)) l = mid+1;
			else
				r = mid;
	}
	
	printf("-1\n%d",l);
	
	return 0;
}

crx CSP-J/S RP++

你可能感兴趣的:(基础题,二分)