题目大意
给出n个矿石,每个矿石都有自己的重量wi 以及价值vi
1、给定m 个区间[Li,Ri];
2、选出一个参数W;
3、对于一个区间[Li,Ri],计算矿石在这个区间上的检验值Yi :这个区间上所有重量大于等于W的矿石数目与它们的价值和的乘积。这批矿产的检验结果Y 为各个区间的检验值之和。
求出S-Y的绝对值最小
题目解析
第一:二分
一看题目,很明显是要枚举W值,取求Y值,并找出S-Y的绝对值的最小值
所以利用二分枚举W的值
因为选择的矿石的重量大于等于W
所以W越大,所选的矿石就越少,Y值越小;W越小,所选的矿石越多,Y值越大
所以Y随X的增大而减小
由此推出二分的条件:
当Y>S 时,需要增大W来减小Y,从而|Y-s|变小;
当Y=s时,|Y-s|==0;
当Y<s时,需要减小W来增大Y,从而|Y-s|变大;
第二:前缀和
我们在计算区间的和时,通常是用前缀和的方法来缩减时间,直接模拟是n2的,而前缀和成了2n
矿石价值和以及矿石数量和
所有区间满足条件的 (矿石数量 * 矿石价值)的和
求最小值
代码
#include<bits/stdc++.h>
#define N 200005
using namespace std;
long long n,m,S,W,sum,ans;
long long w[N],v[N],l[N],r[N],a[N],b[N];
bool check()
{
sum=0;
memset(a,0,sizeof(a));
memset(b,0,sizeof(b));
//a表示满足条件的矿石数量的前缀和,b表示满足条件的矿石价值的前缀和
for(int i=1;i<=n;i++)
if(W<=w[i])//满足条件重量大于等于W
{
a[i]=a[i-1]+1;
b[i]=b[i-1]+v[i];
}
else
{
a[i]=a[i-1];
b[i]=b[i-1];
}
for(int i=1;i<=m;i++)
sum+=(a[r[i]]-a[l[i]-1])*(b[r[i]]-b[l[i]-1]);
if(sum>S) return true;
return false;
}
int main()
{
cin>>n>>m>>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];
int left=1,right=1e6,mid;
while(left<=right)//二分
{
mid=(left+right)>>1;
W=mid;
if(check()) left=mid+1;
else right=mid-1;
if(ans==0&&mid==500000)//随便写的,应付一下刚开始ans=0的情况
ans=abs(sum-S);
ans=min(ans,abs(sum-S));//更新值
}
cout<<ans;
}