洛谷题解:P1314 [NOIP2011 提高组] 聪明的质监员
题目:
小T 是一名质量监督员,最近负责检验一批矿产的质量。这批矿产共有 n个矿石,从 1 到 n 逐一编号,每个矿石都有自己的重量 wi以及价值 vi 。检验矿产的流程是:
1 、给定m个区间 [li,ri]
2 、选出一个参数 W;
3 、对于一个区间 [li,ri],计算矿石在这个区间上的检验值 yi :
其中 j 为矿石编号。
这批矿产的检验结果 y 为各个区间的检验值之和。即:
若这批矿产的检验结果与所给标准值 s 相差太多,就需要再去检验另一批矿产。小T 不想费时间去检验另一批矿产,所以他想通过调整参数 W 的值,让检验结果尽可能的靠近标准值 s,即使得 ∣s−y∣ 最小。请你帮忙求出这个最小值。
输入格式:
第一行包含三个整数 n,m,分别表示矿石的个数、区间的个数和标准值。
接下来的 n 行,每行两个整数,中间用空格隔开,第 i+1 行表示 i 号矿石的重量 wi 和价值 vi。
接下来的 m 行,表示区间,每行两个整数,中间用空格隔开,第 i+n+1行表示区间 [li,ri] 的两个端点 li 和 ri。注意:不同区间可能重合或相互重叠。
输出格式:
一个整数,表示所求的最小值。
样例:
输入:
5 3 15
1 5
2 5
3 5
4 5
5 5
1 5
2 4
3 3
输出:
10
分析:
可以看出来,这个函数是随W的增加递减的,由于要对W进行查找遍历,所以使用二分,找到一个结果最接近s的W。两个端点的值自然是w数组的最小值与最大值。
然后check()
的就是公式结果的值与s的大小关系,如果ans > s
,说明ans还需要再小,函数还是单减的,所以要选择W更大的区间。
然后对于这个函数值的计算,对于每一个W,都要循环m次,每一次循环内还要从l[i]
到r[i]
再循环进行判断,这样执行了TLE了。看来是需要用前缀和,这样就免去了从l[i]
到r[i]
的循环的复杂度,但是需要提前对数组单另判断一次,不符合要求的不自增。
代码如下:
#include<iostream>
#include<string>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<vector>
using namespace std;
long long n, m, s;
long long w[300005] = {
0};
long long v[300005] = {
0};
long long l[300005] = {
0};
long long r[300005] = {
0};
long long pre_w[300005] = {
0};
long long pre_v[300005] = {
0};
long long mini = 2147483647;
long long maxi = -1;
long long result = 0;
long long C_Y(long long W)
{
long long ans = 0;
memset(pre_v,0,sizeof(pre_v));
memset(pre_w,0,sizeof(pre_w));
for(long long i = 1;i <= n;i++)
{
if(w[i] >= W)
{
pre_w[i] = pre_w[i - 1] + 1;
pre_v[i] = pre_v[i - 1] + v[i];
}
else
{
pre_w[i] = pre_w[i - 1];
pre_v[i] = pre_v[i - 1];
}
}
for(long long i = 1;i <= m;i++)
{
ans += (pre_w[r[i]] - pre_w[l[i] - 1]) * (pre_v[r[i]] - pre_v[l[i] - 1]);
}
return ans;
}
bool check(long long ans)
{
result = llabs(ans - s);
if(ans > s)
{
return true;
}
else
{
return false;
}
}
int main()
{
cin>>n>>m>>s;
for(long long i = 1;i <= n;i++)
{
cin>>w[i]>>v[i];
maxi = max(maxi, w[i]);
mini = min(mini, w[i]);
}
for(long long j = 1;j <= m;j++)
{
cin>>l[j]>>r[j];
}
long long left = mini, right = maxi;
long long mid;
long long ans = 0x3f3f3f3f3f3f3f3f;
while(left <= right)
{
mid = (right + left) / 2;
if(check(C_Y(mid)))
{
left = mid + 1;
}
else
{
right = mid - 1;
}
if(ans > result)
{
ans = result;
}
}
cout<<ans<<endl;
return 0;
}