持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第5天,点击查看活动详情
这应该是本蒟蒻第一道不看题解写出来的绿题(虽然大佬可能觉得很水(轻点骂)),我们来看题。
[NOIP2011 提高组] 聪明的质监员
题目描述
小T 是一名质量监督员,最近负责检验一批矿产的质量。这批矿产共有 个矿石,从 到 逐一编号,每个矿石都有自己的重量 以及价值 。检验矿产的流程是:
1 、给定 个区间 ;
2 、选出一个参数 ;
3 、对于一个区间 ,计算矿石在这个区间上的检验值 :
其中 为矿石编号。
这批矿产的检验结果 为各个区间的检验值之和。即:
若这批矿产的检验结果与所给标准值 相差太多,就需要再去检验另一批矿产。小T 不想费时间去检验另一批矿产,所以他想通过调整参数 的值,让检验结果尽可能的靠近标准值 ,即使得 最小。请你帮忙求出这个最小值。
输入格式
第一行包含三个整数 ,分别表示矿石的个数、区间的个数和标准值。
接下来的 行,每行两个整数,中间用空格隔开,第 行表示 号矿石的重量 和价值 。
接下来的 行,表示区间,每行两个整数,中间用空格隔开,第 行表示区间 的两个端点 和 。注意:不同区间可能重合或相互重叠。
输出格式
一个整数,表示所求的最小值。
样例 #1
样例输入 #1
5 3 15
1 5
2 5
3 5
4 5
5 5
1 5
2 4
3 3
样例输出 #1
10
提示
【输入输出样例说明】
当 选 的时候,三个区间上检验值分别为 ,这批矿产的检验结果为 ,此时与标准值 相差最小为 。
【数据范围】
对于 的数据,有 ;
对于 的数据,有 ;
对于 的数据,有 ;
对于 的数据,有 ;
对于 的数据,有 ,,, 。
有一说一,受高中数学的影响,我第一眼竟然看不懂 这个式子,后来发现这个式子意思是在区间内值的矿产品的个数之和与这些矿产品对应的值的和的乘积。
分析
现在我们正式对这题分析一下,这题的题意就是给定每个矿产品的和值,给了个区间,要我们找出一个参数,使得每个区间里面的矿产品数量之和与它们对应的值之和的乘积之和与给定的差的绝对值最小。看似有点绕,其实,第一思路很直接,从开始枚举的值,然后取最小的的值......(编不下去了,复杂度直接爆炸),这里代码就不展示了,然后第二思路,我们发现对的取值明显是一个单调的过程,因此我们想到了二分答案,二分的值,然后枚举每个区间,找到最小的那个,具体代码实现
代码1
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <string>
#include <queue>
#include <vector>
#include <map>
#include <set>
#include <stack>
#include <cmath>
#include <iomanip>
#define int long long
#define AC return
#define Please 0
using namespace std;
const int N=201010;
const double eps=1e-6;
int n,m,s;
int le[N],ri[N],maxn=-1;
typedef pair<int,int>PII;
typedef unsigned long long ull;
bool flag[N];
inline int read(){//快读
int x=0,f=1;char ch=getchar();
while(ch<'0' || ch>'9'){
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0' && ch<='9'){
x=x*10+ch-'0';
ch=getchar();
}
AC x*f;
}
struct good{
int w,v;
}a[N];
good b[N];
int check(int x){
int sum=0;
for(int i=1;i<=n;i++){
b[i]=a[i];
}
for(int i=1;i<=m;i++){
int num=0,val=0;
for(int j=le[i];j<=ri[i];j++){
if(a[j].w>=x){
num++;
val+=a[j].v;
}
}
sum+=num*val;
}
return sum;
}
signed main(){
cin>>n>>m>>s;
for(int i=1;i<=n;i++){
cin>>a[i].w>>a[i].v;
maxn=max(maxn,a[i].w);
}
for(int i=1;i<=m;i++){
cin>>le[i]>>ri[i];
}
int l=0,r=maxn+1,ans=1e18;
while(l<r){
int mid=l+r+1>>1;
ans=min(ans,abs(check(mid)-s));
if(check(mid)>=s){
l=mid;
}
else r=mid-1;
}
cout<<ans<<endl;
AC Please;
}
当然这个复杂度显然过不去因为也几乎飞了,于是得到了分,随后我就在思考怎么优化(其实大佬一眼就能看出来这个优化),我们发现由于是要对应的的值相加,因此我们考虑到前缀和,只要把的值的和都变成,再把的值都变成,然后可以用前缀和预处理出和的和,那样的复杂度就可以把每个的值算出来了,最后的总时间复杂度是,完美解决问题~。
代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <string>
#include <queue>
#include <vector>
#include <map>
#include <set>
#include <stack>
#include <cmath>
#include <iomanip>
#define int long long
#define AC return
#define Please 0
using namespace std;
const int N=201010;
const double eps=1e-6;
int n,m,s;
int le[N],ri[N],maxn=-1,num[N],value[N];
typedef pair<int,int>PII;
typedef unsigned long long ull;
bool flag[N];
inline int read(){//快读
int x=0,f=1;char ch=getchar();
while(ch<'0' || ch>'9'){
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0' && ch<='9'){
x=x*10+ch-'0';
ch=getchar();
}
AC x*f;
}
struct good{
int w,v;
}a[N];
good b[N];
int check(int x){
int sum=0;
for(int i=1;i<=n;i++){
b[i]=a[i];//先要复制一份值,防止a发生改变
}
for(int i=1;i<=n;i++){
if(b[i].w<x) b[i].w=b[i].v=0;
else b[i].w=1;
}
for(int i=1;i<=n;i++){
num[i]=num[i-1]+b[i].w;
value[i]=value[i-1]+b[i].v;
}
for(int i=1;i<=m;i++){
sum+=(num[ri[i]]-num[le[i]-1])*(value[ri[i]]-value[le[i]-1]);
}
return sum;
}
signed main(){
cin>>n>>m>>s;
for(int i=1;i<=n;i++){
cin>>a[i].w>>a[i].v;
maxn=max(maxn,a[i].w);
}
for(int i=1;i<=m;i++){
cin>>le[i]>>ri[i];
}
int l=0,r=maxn+1,ans=1e18;//l是可以取到0滴,QAQ。
while(l<r){
int mid=l+r+1>>1;
ans=min(ans,abs(check(mid)-s));
if(check(mid)>=s){
l=mid;
}
else r=mid-1;
}
cout<<ans<<endl;
AC Please;
}
这题我还学到了一点,就是在这种取绝对值最小的二分,每次都要取每个的而不是和古老的二分一样,因为在的左右都可能出现正确答案。 希望能帮助到大家()!