洛谷P1083《借教室》
原更新日期:2019-01-24 14:54:42
字首和 + 二分答案
題目描述
在大學期間,經常需要租借教室。大到院系舉辦活動,小到學習小組自習討論,都需要向學校申請借教室。教室的大小功能不同,借教室人的身份不同,借教室的手續也不一樣。
面對海量租借教室的資訊,我們自然希望程式設計解決這個問題。
我們需要處理接下來\(n\)天的借教室資訊,其中第\(i\)天學校有\(r_i\)個教室可供租借。共有\(m\)份訂單,每份訂單用三個正整數描述,分別為\(d_j,s_j,t_j\),表示某租借者需要從第\(s_j\)天到第\(t_j\)天租借教室(包括第\(s_j\)天和第\(t_j\)天),每天需要租借\(d_j\)
我們假定,租借者對教室的大小、地點沒有要求。即對於每份訂單,我們只需要每天提供\(d_j\)個教室,而它們具體是哪些教室,每天是否是相同的教室則不用考慮。
借教室的原則是先到先得,也就是說我們要按照訂單的先後順序依次為每份訂單分配教室。如果在分配的過程中遇到一份訂單無法完全滿足,則需要停止教室的分配,通知當前申請人修改訂單。這裡的無法滿足指從第\(s_j\)天到第\(t_j\)天中有至少一天剩餘的教室數量不足\(d_j\)個。
現在我們需要知道,是否會有訂單無法完全滿足。如果有,需要通知哪一個申請人修改訂單。
輸入輸出格式
輸入格式:
第一行包含兩個正整數\(n,m\),表示天數和訂單的數量。
第二行包含\(n\)個正整數,其中第\(i\)個數為\(r_i\),表示第\(i\)天可用於租借的教室數量。
接下來有\(m\)行,每行包含三個正整數\(d_j,s_j,t_j\),表示租借的數量,租借開始、結束分別在第幾天。
每行相鄰的兩個數之間均用一個空格隔開。天數與訂單均用從\(1\)開始的整數編號。
輸出格式
如果所有訂單均可滿足,則輸出只有一行,包含一個整數 \(0\)。否則(訂單無法完全滿足)
輸出兩行,第一行輸出一個負整數\(−1\),第二行輸出需要修改訂單的申請人編號。
輸入輸出樣例
輸入樣例
4 3
2 5 4 3
2 1 3
3 2 4
4 2 4
輸出樣例
-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 ≤ r_i,d_j≤ 10^9,1 ≤ s_j≤ t_j≤ n $。
NOIP 2012 提高組 第二天 第二題
解題思路
考慮二分答案
首先我們知道,對於一個訂單 \(i\),如果它能被批准,那麼 \([1,i]\) 都能被批准;如果它不能被批准,那麼 \([i,m]\) 都不能被批准(單調性)
那麼我們二分訂單的編號 \(\text{mid}\),每次判一下\([1,\text{mid}]\)是否全都能滿足,最後如果右邊界不是 \(m\) 了,說明有訂單不能滿足,輸出右邊界即可
如何判斷是否能滿足?
首先我們要\(O(1)\)實現區間修改(???)
用字首和就可以實現!
想想下面的過程\(\downarrow\)
原數列: 0 0 0 0 0 0
[ 1 2 3 4 5 6 ]
字首和: 0 0 0 0 0 0
[ 1 2 3 4 5 6 ]
我們讓[1,3]都增加2
於是我們選擇讓[1]增加2,讓[4](即[3+1])減去2
那麼上面的數列就變成了:
原數列: 2 0 0 -2 0 0
[ 1 2 3 4 5 6 ]
字首和: 2 2 2 0 0 0
[ 1 2 3 4 5 6 ]
這個時候字首和陣列就實現了區間加!
那麼依照上面的思想,我們就能寫出Check(int mid)
struct Order {
int amount, l, r;
Order() { amount = l = r = 0; }
} order[MAXM];
int a[MAXN], sum[MAXN];
bool Check(int __i) {
memset(sum, 0, sizeof sum);
for (int i = 1; i <= __i; ++i) {
// 像上面一樣處理字首和
sum[order[i].l] += order[i].amount;
sum[order[i].r] -= order[i].amount;
}
for (int i = 1; i <= n; ++i) {
// 將字首和處理一遍
// 判一下是否有超過當天可用教室的值
sum[i] += sum[i - 1];
if (sum[i] > a[i]) return false; // 不合法
}
return true; // 合法
}
程式碼實現
/* -- Basic Headers -- */
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cctype>
#include <algorithm>
/* -- STL Iterators -- */
#include <vector>
#include <string>
#include <stack>
#include <queue>
/* -- External Headers -- */
#include <map>
#include <cmath>
/* -- Defined Functions -- */
#define For(a,x,y) for (int a = x; a <= y; ++a)
#define Forw(a,x,y) for (int a = x; a < y; ++a)
#define Bak(a,y,x) for (int a = y; a >= x; --a)
namespace FastIO {
inline int getint() {
int s = 0, x = 1;
char ch = getchar();
while (!isdigit(ch)) {
if (ch == '-') x = -1;
ch = getchar();
}
while (isdigit(ch)) {
s = s * 10 + ch - '0';
ch = getchar();
}
return s * x;
}
inline void __basic_putint(int x) {
if (x < 0) {
x = -x;
putchar('-');
}
if (x >= 10) __basic_putint(x / 10);
putchar(x % 10 + '0');
}
inline void putint(int x, char external) {
__basic_putint(x);
putchar(external);
}
}
namespace Solution {
const int MAXNM = 1000000 + 10;
struct Order {
int num;
int l, r;
Order() { num = l = r = 0; }
} order[MAXNM];
int n, m, seq[MAXNM];
int sum[MAXNM];
bool Check(int M) {
memset(sum, 0, sizeof sum);
for (int i = 1; i <= M; ++i) {
sum[order[i].l] += order[i].num;
sum[order[i].r + 1] -= order[i].num;
}
for (int i = 1; i <= n; ++i) {
sum[i] += sum[i - 1];
if (sum[i] > seq[i]) return false;
}
return true;
}
}
signed main() {
#define HANDWER_FILE
#ifndef HANDWER_FILE
freopen("testdata.in", "r", stdin);
freopen("testdata.out", "w", stdout);
#endif
using namespace Solution;
using namespace FastIO;
n = getint();
m = getint();
For (i, 1, n) seq[i] = getint();
For (i, 1, m) {
order[i].num = getint();
order[i].l = getint();
order[i].r = getint();
}
int L = 1, R = m;
while (L < R) {
int mid = (L + R) >> 1;
if (Check(mid)) L = mid + 1;
else R = mid;
}
if (R != m) {
printf("-1\n%d\n", R);
} else puts("0");
return 0;
}