原题:[www.luogu.com.cn/problem/P13…] 这是一道浪漫的题目
题面:
P1314 [NOIP 2011 提高组] 聪明的质监员
题目描述
小 T 是一名质量监督员,最近负责检验一批矿产的质量。这批矿产共有 个矿石,从 到 逐一编号,每个矿石都有自己的重量 以及价值 。检验矿产的流程是:
- 给定 个区间 ;
- 选出一个参数 ;
- 对于一个区间 ,计算矿石在这个区间上的检验值 :
其中 为矿石编号, 是指示函数,若条件 为真返回 ,否则返回 。
这批矿产的检验结果 为各个区间的检验值之和。即:。
若这批矿产的检验结果与所给标准值 相差太多,就需要再去检验另一批矿产。小 T 不想费时间去检验另一批矿产,所以他想通过调整参数 的值,让检验结果尽可能的靠近标准值 ,即使得 最小。请你帮忙求出这个最小值。
输入格式
第一行包含三个整数 ,分别表示矿石的个数、区间的个数和标准值。
接下来的 行,每行两个整数,中间用空格隔开,第 行表示 号矿石的重量 和价值 。
接下来的 行,表示区间,每行两个整数,中间用空格隔开,第 行表示区间 的两个端点 和 。注意:不同区间可能重合或相互重叠。
输出格式
一个整数,表示所求的最小值。
输入输出样例 #1
输入 #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 <cstring>
#include <iomanip>
#include <cmath>
#include <vector>
#include <algorithm>
#include <climits>
using namespace std;
#define ll long long
#define ull unsigned long long
#define debug(x) cout << #x << "=" << x << "\n";
int n, m;
ll s;
const int maxn = 2e5 + 10;
ll w[maxn], v[maxn];
int l[maxn], r[maxn];
ll diff[maxn], cnt[maxn];
ll max_val = 0, min_val = LLONG_MAX;
ll ans = LLONG_MAX;
ll calc(ll W)
{
static ll pre_cnt[maxn], pre_val[maxn];
pre_cnt[0] = pre_val[0] = 0;
for (int i = 1; i <= n; i++)
{
pre_cnt[i] = pre_cnt[i - 1] + (w[i] >= W);
pre_val[i] = pre_val[i - 1] + (w[i] >= W ? v[i] : 0);
}
ll res = 0;
for (int i = 1; i <= m; i++)
{
ll now_cnt = pre_cnt[r[i]] - pre_cnt[l[i] - 1];
ll now_val = pre_val[r[i]] - pre_val[l[i] - 1];
res += now_cnt * now_val;
}
return res;
}
void binary(ll left, ll right)
{
ll mid;
while (left <= right)
{
mid = ((left + right) >> 1);
ll res = calc(mid);
ans = min(ans, llabs(res - s));
if (res > s)
left = mid + 1;
else
right = mid - 1;
}
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n >> m >> s;
for (int i = 1; i <= n; i++)
{
cin >> w[i] >> v[i];
max_val = max(max_val, w[i]);
min_val = min(min_val, w[i]);
}
for (int i = 1; i <= m; i++)
cin >> l[i] >> r[i];
binary(min_val, max_val);
cout << ans;
return 0;
}
注意开 !血的教训