上链接:https://www.luogu.com.cn/problem/P1314
上题干:
小T
是一名质量监督员,最近负责检验一批矿产的质量。这批矿产共有 n 个矿石,从 11 到 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说明/提示
【输入输出样例说明】
当 W 选 44 的时候,三个区间上检验值分别为 20,5,020,5,0 ,这批矿产的检验结果为 2525,此时与标准值 �S 相差最小为 1010。
【数据范围】
对于 10%10% 的数据,有1≤n,m≤10;
对于 30%30%的数据,有 1≤n,m≤500 ;
对于 50%50% 的数据,有1≤n,m≤5,000;
对于 70%70% 的数据,有 1≤n,m≤10,000 ;
对于 100%100% 的数据,有 1≤n,m≤200,000(mid的取值),0<wi,vi≤10^6,0<s≤10^12,1≤li≤ri≤n
思路:
首先这道题是没有给出w的,并且我们要注意他出现了这样的语句“通过调整参数W,的值,让检验结果尽可能靠近s,即|s-y|尽可能小”。
我们就应该知道,这道题应该用二分来求w。
我们再看,有关W的条件:
当每个重量w都大于参数的时候,我们才计算答案。
说明W参数的取值范围应该是w【i】取值范围的极限,【1,200000+10】;
那么这样一来二分的三要素,mid,l,r都找好了
现在来找二分的条件,因为我们需要让y尽可能地接近s,所以我们每一次比较W不同的时候,y与s的关系,当y小于等于s,说明此时的参数大了,那么答案就应该在mid的左区间,也就是r=mid
当y大于s,说明此时的参数小了,答案应该在mid的右区间,也就是l=mid+1;
想到这些,这道题已经完成了一半了。
剩下的一半就是怎么去处理多余的数据。
我们再再再注意看这样的一句话:每次给出一个区间,求矿石在这个区间上面的检验值。
是不是很眼熟啊,这就是前缀和的标准出场动画了。
但是!!!我们求前缀和是需要满足w【i】>=W的,因为W参数就是我们二分的mid,所以我们只能在二分的循环里面求了,但是我们可以先把每一次要输入的边界l,r给他存到数组里,这样我们二分的时候就不需要再输入了。
然后我们再用一个变量不断更新二分过程中 | y-s |的 最小值。二分结束之后输出这个最小值就行了
上代码:
const int N = 2e5 + 10;
typedef long long LL;
LL n, m, s,y;矿石的个数,区间的个数,标准值,检验结果
int w[N], v[N];每个矿石的重量和价值
LL sw[N], sv[N];
int l[N], r[N];每次输入的左右区间
int L, R,mid;二分的三要数
LL t;
int main()
{
cin >> n >> m >> s;
t = s;
for (int i = 1; i <= n; i++)cin >> w[i] >> v[i];
for (int i = 1; i <= m; i++)cin >> l[i] >> r[i];//存放每次输入的区间
L = 1, R=N;
while (L < R)
{
memset(sw, 0, sizeof(sw));
memset(sv, 0, sizeof(sv));
mid = (L + R) >> 1;
y = 0;
for (int i = 1; i <= n; i++)
{
if (w[i] >= mid) {
sw[i] = sw[i - 1] + 1;
sv[i] = sv[i - 1] + v[i];
}
else
{
sv[i] = sv[i - 1];
sw[i] = sw[i - 1];
}
}
for (int i = 1; i <= m; i++)
y +=(sw[r[i]] - sw[l[i]-1]) * (sv[r[i]] - sv[l[i]-1]);
if (abs(y - s) == 0) {
t = 0;
break;
}
t = min(t, abs(s-y));
if (y <= s)R = mid;
else if(y>s) L = mid + 1;
}
cout << t;
}