題目
[NOIP2011 提高組] 聰明的質監員
題目描述
小T 是一名質量監督員,最近負責檢驗一批礦產的質量。這批礦產共有 n 個礦石,從 1 到 n 逐一編號,每個礦石都有自己的重量 wi 以及價值 vi 。檢驗礦產的流程是:
- 給定m 個區間 [li,ri];
- 選出一個引數 W;
- 對於一個區間 [li,ri],計算礦石在這個區間上的檢驗值 yi:
其中 j 為礦石編號。
這批礦產的檢驗結果 y 為各個區間的檢驗值之和。即:
若這批礦產的檢驗結果與所給標準值 s 相差太多,就需要再去檢驗另一批礦產。小T 不想費時間去檢驗另一批礦產,所以他想透過調整引數 W 的值,讓檢驗結果儘可能的靠近標準值 s,即使得 |s-y|最小。請你幫忙求出這個最小值。
輸入格式
第一行包含三個整數 n,m,s,分別表示礦石的個數、區間的個數和標準值。
接下來的 n 行,每行兩個整數,中間用空格隔開,第 i+1 行表示 i 號礦石的重量 wi 和價值 vi。
接下來的 m 行,表示區間,每行兩個整數,中間用空格隔開,第 i+n+1 行表示區間 [li,ri] 的兩個端點 li 和 ri。注意:不同區間可能重合或相互重疊。
輸出格式
一個整數,表示所求的最小值。
樣例 #1
5 3 15
1 5
2 5
3 5
4 5
5 5
1 5
2 4
3 3
輸出 #1
10
題解
#include <iostream>
#include <algorithm>
#include <cstdlib>
#include <cmath>
const int N = 2e5 + 10;
long long n, m, goal;
int w[N], v[N], l[N], r[N]; // w 為礦石的重量,v 為礦石的價值,l 和 r 為區間的起始和結束位置
long long a[N], s[N]; // a 是儲存字首和的陣列,s 是儲存字首和的礦石價值陣列
using namespace std;
// 檢查給定 W 值時的檢驗結果
long long check(int mid);
int main() {
cin >> n >> m >> goal;
// 輸入礦石的重量和價值
for(long long i = 1; i <= n; i++) {
cin >> w[i] >> v[i];
}
// 輸入區間的起始和結束位置
for(long long i = 0; i < m; i++) {
cin >> l[i] >> r[i];
}
// 二分查詢的右邊界是 w 的最大值 + 1
long long l = 0, r = 1e6 + 1;
while(l < r) {
long long mid = l + r + 1 >> 1;
if(check(mid) >= goal) {
l = mid; // 說明當前 W 值可以滿足條件,繼續嘗試更大的 W
} else {
r = mid - 1; // 如果不能滿足目標值,嘗試更小的 W
}
}
// 輸出最接近目標的結果,檢查 l 和 r 對應的檢驗值差值
cout << min(abs(check(r) - goal), abs(check(r + 1) - goal)) << endl;
}
// 計算給定 W 值時的檢驗值
long long check(int mid) {
long long y = 0;
// 更新字首和陣列
for(long long i = 1; i <= n; i++) {
if(w[i] >= mid) {
a[i] = a[i - 1] + 1; // 礦石重量大於等於 W,數量 +1
s[i] = s[i - 1] + v[i]; // 礦石價值加上
} else {
a[i] = a[i - 1];
s[i] = s[i - 1];
}
}
// 計算所有區間的檢驗值
for(long long i = 0; i < m; i++) {
// 根據字首和計算每個區間的檢驗值
long long count = a[r[i]] - a[l[i] - 1]; // 區間內滿足條件的礦石數量
long long value_sum = s[r[i]] - s[l[i] - 1]; // 區間內滿足條件的礦石的價值和
y += count * value_sum; // 計算檢驗值並累加
}
return y;
}