提高+省选-专题[二分][前缀和]:P1314 聪明的质监员
我这里推荐一下我的博客
在博客里观看更美观哦~
题目
华丽的分割线
解析
单调性
对于一个参数W,W越大,可选的值就越少,Y的值也越小
反过来,W越小,Y越大,如果Y是W的函数,那么该函数单调递减
可以考虑二分W,找到距离S最近的Y值
图像如下
这个函数奇怪之处在于它在S附近不连续
我们需要比较S附近那两个点与S的距离
在我的二分写法中这两个点从左到右分别为r,l
函数值
现在对于给定的W,如何用O(n)时间求出Y
先预处理出sum[i],和num[i],
sum[i]表示1~i中所有wi>=W的vi之和,num[i]则表示个数
i从1到n枚举,若wi<W,则sum[i]=sum[i-1],num[i]=num[i-1]
若wi>=W,则sum[i]=sum[i-1]+v[i],num[i]=num[i-1]+1
对于每个区间li,ri,ans+=(sum[ri]-sum[li-1])*(num[ri]-num[li-1])
返回ans即可
二分
初始化l=0,r=1e6,若f(mid)>s,则l向上移动,l=mid+1
否则r向下移动,r=mid-1,循环条件为l<=r
也就是说,当r>l时停止,所以左面的点为r,右面为l
ans=min(labs(f(l)-s),labs(f(r)-s))
代码
#include<cstdio>
#include<cmath>
#define ll long long
using namespace std;
const int N=1001000;
template<class T>
void read(T &x)
{
x=0;
T f;
f=1;
char c;
c=getchar();
while((c<'0'||c>'9')&&c!='-')
{
c=getchar();
}
if(c=='-')
{
f=-1;
c=getchar();
}
while(c>='0'&&c<='9')
{
x=(x<<3)+(x<<1)+(c^48);
c=getchar();
}
x=x*f;
}
int w[N],v[N],L[N],R[N],n,m;
ll s,sum[N],num[N];
ll check(ll W)
{
int i;
ll ans;
ans=0;
for(i=1;i<=n;i++)
{
if(w[i]<W)
{
num[i]=num[i-1];
sum[i]=sum[i-1];
}
else
{
sum[i]=sum[i-1]+v[i];
num[i]=num[i-1]+1;
}
}
for(i=1;i<=m;i++)
{
ans+=(sum[R[i]]-sum[L[i]-1])*(num[R[i]]-num[L[i]-1]);
}
return ans;
}
inline ll labs(ll a)
{
return a>0 ? a : -a;
}
int main()
{
int i;
ll l,r,mid,ans;
read(n);
read(m);
read(s);
for(i=1;i<=n;i++)
{
read(w[i]);
read(v[i]);
}
for(i=1;i<=m;i++)
{
read(L[i]);
read(R[i]);
}
l=0;
r=1e6;
while(l<=r)
{
mid=(l+r)>>1;
if(check(mid)>s)
{
l=mid+1;
}
else
{
r=mid-1;
}
}
ans=labs(check(l)-s);
if(ans>labs(check(r)-s))
{
ans=labs(check(r)-s);
}
printf("%lld",ans);
}