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 <bits/stdc++.h>
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 <bits/stdc++.h>
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 <bits/stdc++.h>
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++

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章