《深入浅出进阶篇》——洛谷P1314——二分+前缀和

上链接:https://www.luogu.com.cn/problem/P1314

上题干:

小T 是一名质量监督员,最近负责检验一批矿产的质量。这批矿产共有 n 个矿石,从 11 到 n 逐一编号,每个矿石都有自己的重量 wi​ 以及价值 vi​ 。检验矿产的流程是:

  1. 给定m 个区间 [li​,ri​];
  2. 选出一个参数 W;
  3. 对于一个区间 [li​,ri​],计算矿石在这个区间上的检验值 yi​:

y_{i}=\sum_{j=l_{i}}^{r_{i}}[w_{j}>=W] * \sum_{j=l_{i}}^{r_{i}}[w_{j}>=W]v_{j}

其中 j 为矿石编号。这批矿产的检验结果 y 为各个区间的检验值之和。即:\sum_{i=1}^{m}y_{i};

若这批矿产的检验结果与所给标准值 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的条件:

y_{i}=\sum_{j=l_{i}}^{r_{i}}[w_{j}>=W] * \sum_{j=l_{i}}^{r_{i}}[w_{j}>=W]v_{j}

当每个重量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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

louisdlee.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值