写在前面
昨天说好“明天见”的,我还算信守承诺吧?
蒟蒻还是那个蒟蒻,依然是要叨叨,不过,今天要讲的,是本蒟蒻人生第一道绿题!
本题目来自洛谷,网址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\):
其中 \(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\)s
。s-get_sum(R)
亦然。
时间复杂度
初步猜测,时间复杂度为\(2n\)?
洛谷运行结果如下:
时间复杂度真心不高,连个过100\(ms\)的都没有。
不过空间复杂度确实高了点。(反正空间又不值钱)
写在最后
人生第一道绿题!噢耶!
您能看到这,真是太了不起了!
这次题解花了不少时间,感觉好像又打回原形了qwq
欢迎各位纠错、优化!