写在前面

昨天说好“明天见”的,我还算信守承诺吧?
蒟蒻还是那个蒟蒻,依然是要叨叨,不过,今天要讲的,是本蒟蒻人生第一道绿题!
本题目来自洛谷,网址https://www.luogu.com.cn/problem/P1314。建议自己试试。
本题解非营利性,无恶意,无侵权目的。如有侵犯您的著作权或其他知识产权抑或任何权利,还请多多包涵,与作者联系删除!
蒟蒻水平低下,错误连篇,欢迎您找茬批评指正!

题面(复制于洛谷)

[NOIP2011 提高组] 聪明的质监员

题目描述

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

1 、给定$ m$ 个区间 \([l_i,r_i]\)

2 、选出一个参数 \(W\)

3 、对于一个区间 \([l_i,r_i]\),计算矿石在这个区间上的检验值 \(y_i\)

\[y_i=\sum\limits_{j=l_i}^{r_i}[w_j \ge W] \times \sum\limits_{j=l_i}^{r_i}[w_j \ge W]v_j \]

其中 \(j\) 为矿石编号。

这批矿产的检验结果 \(y\) 为各个区间的检验值之和。即:\(\sum\limits_{i=1}^m y_i\)

若这批矿产的检验结果与所给标准值 \(s\) 相差太多,就需要再去检验另一批矿产。小T 不想费时间去检验另一批矿产,所以他想通过调整参数 \(W\) 的值,让检验结果尽可能的靠近标准值 \(s\),即使得 \(|s-y|\) 最小。请你帮忙求出这个最小值。

输入格式

第一行包含三个整数 \(n,m,s\),分别表示矿石的个数、区间的个数和标准值。

接下来的 \(n\) 行,每行两个整数,中间用空格隔开,第 \(i+1\) 行表示 \(i\) 号矿石的重量 \(w_i\) 和价值 \(v_i\)

接下来的 \(m\) 行,表示区间,每行两个整数,中间用空格隔开,第 \(i+n+1\) 行表示区间 \([l_i,r_i]\) 的两个端点 \(l_i\)\(r_i\)。注意:不同区间可能重合或相互重叠。

输出格式

一个整数,表示所求的最小值。

样例 #1

样例输入 #1

5 3 15 
1 5 
2 5 
3 5 
4 5 
5 5 
1 5 
2 4 
3 3

样例输出 #1

10

提示

【输入输出样例说明】

\(W\)\(4\) 的时候,三个区间上检验值分别为 \(20,5 ,0\) ,这批矿产的检验结果为 \(25\),此时与标准值 \(S\) 相差最小为 \(10\)

【数据范围】

对于 $10% $ 的数据,有 \(1 ≤n ,m≤10\)

对于 $30% $的数据,有 \(1 ≤n ,m≤500\)

对于 $50% $ 的数据,有 $ 1 ≤n ,m≤5,000$;

对于 \(70\%\) 的数据,有 \(1 ≤n ,m≤10,000\)

对于 \(100\%\) 的数据,有 $ 1 ≤n ,m≤200,000$,\(0 < w_i,v_i≤10^6\)\(0 < s≤10^{12}\)\(1 ≤l_i ≤r_i ≤n\)

题目解释

首先,我要说的是,这家伙这么偷懒,还随意改变标准,确定不会丢饭碗?!
玩完了,切入正题。不得不说,这题信息量蛮大的,但读完总结总结,也就那么点东西。
他要你干什么呢?
\(n\)\(w\)\(n\)\(v\),从\(l\)\(r,w\)大于\(W\)中所有的\(v\)相加,乘上这些编号的和,就是\(y\)
\(m\)\(l\)\(m\)\(r\),这之中所有\(y\)相加,跟s比较,差值越小越好。
\(n,m,w,v,l,r,\)这些都给你,自己找一个\(W\),然后输出最小差值。
就这样,结束了。
那怎么办到呢?
既然从\(l\)\(r\)求和,是不是会用到前缀和啊?(前缀和不知道的可以\(bdfs\)一下,仔细学学,可有用了)
那然后,要自己找\(W\),是不是按大小顺序找啊?(从计算公式可得,\(W\)越大,\(y\)越小)既然从小往大,是不是可以用二分啊?
这么着,整道题就迎刃而解了。

代码实现

首先,永不变的前期准备

const int N=200005;
int w[N]; //存储重量 
int v[N]; //存储价值 
int sum_cnt[N];//记录下标 
int m,n;//意义见题面 
long long s;
int li[N],ri[N]; //记录m个区间 
long long sum_v[N];//记录矿产价值前缀和 

其次,计算每个区间的y值

long long get_y(int li, int ri, int W)//计算区间检测值y 
{
	int cnt=sum_cnt[ri]-sum_cnt[li-1];//获得下标差值 
	long long ret=sum_v[ri]-sum_v[li-1];//获得总价值 
	return cnt*1LL*ret;//返回y值(即cnt*ret) 
}

再次,计算总和

long long get_sum(int W)
{
	for(int i=1;i<=n;i++)// 初始化
	{ 
		if(w[i]>=W)//只有重量达到参数才予以计数 
		{ 
			sum_cnt[i]=1;//符合条件时,下标记录赋1,用于进行计算 
			sum_v[i]=v[i];//价值前缀和先用自身价值 
		}
		else
		{
			sum_cnt[i]=0;//不满足条件则不予记录 
			sum_v[i]=0;
		}
	} 
	for(int i=1;i<=n;i++)//前缀和 
	{
		sum_cnt[i]+=sum_cnt[i-1];//下标为每个符合条件的矿石在符合条件的序列中的位置 
		sum_v[i]+=sum_v[i-1];//计算价值前缀和 
	}
	long long ret=0;//ret代表各区间y值之和 
	for(int i=1;i<=m;i++)//遍历区间 
		ret+=get_y(li[i],ri[i],W);//求该区间y值并求和 	
	return ret;//返回检验结果 
}

最后,主函数里一输入,一查找,一输出,完事

int main()
{
	scanf("%d%d%lld",&n,&m,&s);
	for(int i=1;i<=n;i++)
		scanf("%d%d",&w[i],&v[i]);
	for(int i=1;i<=m;i++)
		scanf("%d%d",&li[i],&ri[i]);	
	int L=0;//W=0时,get_sum是最大的,保证结果>=s 
	int R=1e6+1;//此时get_sum是最小的,保证结果<s 
	while(L+1<R)//二分查找直到L>=R,此时查找到目标 
	{
		int mid=(L+R)>>1;//右移1,即相当于除以2。取L与R的中间值 
		if(get_sum(mid)>=s)//若该参数下总和大于或等于目标s 
			L=mid;//查找右半边 
		else
			R=mid;//否则查找左半边 
	}
	printf("%lld",min(get_sum(L)-s,s-get_sum(R)));//输出较小值 
	return 0;
}

完整代码

#include<bits/stdc++.h>
using namespace std;
const int N=200005;
int w[N];
int v[N];
int sum_cnt[N];
int m,n;
long long s;
int li[N],ri[N]; 
long long sum_v[N];
long long get_y(int li, int ri, int W) 
{
	int cnt=sum_cnt[ri]-sum_cnt[li-1]; 
	long long ret=sum_v[ri]-sum_v[li-1];
	return cnt*1LL*ret; 
}
long long get_sum(int W)
{
	for(int i=1;i<=n;i++)
	{ 
		if(w[i]>=W)
		{ 
			sum_cnt[i]=1;
			sum_v[i]=v[i];
		}
		else
		{
			sum_cnt[i]=0;
			sum_v[i]=0;
		}
	} 
	for(int i=1;i<=n;i++)
	{
		sum_cnt[i]+=sum_cnt[i-1];
		sum_v[i]+=sum_v[i-1];
	}
	long long ret=0;
	for(int i=1;i<=m;i++)
		ret+=get_y(li[i],ri[i],W);	
	return ret;
}
int main()
{
	scanf("%d%d%lld",&n,&m,&s);
	for(int i=1;i<=n;i++)
		scanf("%d%d",&w[i],&v[i]);
	for(int i=1;i<=m;i++)
		scanf("%d%d",&li[i],&ri[i]);	
	int L=0;
	int R=1e6+1;
	while(L+1<R)
	{
		int mid=(L+R)>>1;
		if(get_sum(mid)>=s)
			L=mid;
		else
			R=mid;
	}
	printf("%lld",min(get_sum(L)-s,s-get_sum(R)));
	return 0;
}

请注意

  • get_y函数内的1ll是一种特殊记法,即long long下的\(1\),常用来乘上目标数字以达到数据类型转换的目的
  • 本题目数据范围可谓是千里之差。为了更好地理解,如果容易混淆,那就全开long long
  • 二分查找一定要注意边界,不然会酿成大祸的
  • 输出时get_sum(L)-s是因为经过循环,L\(\geq\)R。此时,get_sum\(\geq\)ss-get_sum(R)亦然。

时间复杂度

初步猜测,时间复杂度为\(2n\)?
洛谷运行结果如下:

时间复杂度真心不高,连个过100\(ms\)的都没有。
不过空间复杂度确实高了点。(反正空间又不值钱)

写在最后

人生第一道绿题!噢耶!
您能看到这,真是太了不起了!
这次题解花了不少时间,感觉好像又打回原形了qwq
欢迎各位纠错、优化!

THE END~

posted on 2023-07-16 20:42  httony  阅读(62)  评论(0编辑  收藏  举报