H小明买年糕(前缀和+二分)
Description
过年了,小明准备去超市买年糕,超市里共有nn种年糕,每种年糕数量无限多,从1~n编号,每种年糕有自己的价格wiwi和美味值vivi。小明选购年糕的过程如下:
1、小明打算去买m次年糕,每次选购区间[Li, Ri][Li,Ri]内的年糕,每种年糕每次只能买一个;
2、每次选购都用事先准备好的购物袋,购物袋可以装任意数量的物品,但是这个购物袋只装价格wiwi不小于WW的年糕,区间内所有满足条件的年糕都必须要买;
3、每次出去购买年糕都会产生一个幸福值YiYi,它的计算的公式为:
Y_i=(\sum\limits_{j=L_i}^{R_i}[w_j\geq W]\times v_j)\times (\sum\limits_{j=L_i}^{R_i}[w_j\geq W])Y
i
=(
j=L
i
∑
R
i
[w
j
≥W]×v
j
)×(
j=L
i
∑
R
i
[w
j
≥W]),其中符号[条件]的意思是:若条件为真,值就是1,否则值就是0;
所以小明m次选购年糕行动所产生的总幸福值为:H=Y_1+Y_2+…+Y_mH=Y
1
+Y
2
+…+Y
m
;
小明是一个精简持家的程序员,所以他选购年糕有一个标准值SS。他想通过调整事先准备的购物袋的WW值,让年糕总幸福值尽量接近SS,即使得S-HS−H的绝对值最小。现在请你帮忙调整购物袋的WW值,使得|S-H|∣S−H∣绝对值最小,并输出这个最小值。
20190220213730804.jpeg
Input
第一行包括三个整数nn, mm, SS,分别表示年糕的种类,购物的次数和标准值。
接下来nn行,每行两个整数w_iw
i
, v_iv
i
,中间用空格隔开,表示第ii个年糕的价格w_iw
i
和美味值v_iv
i
。
接下来mm行,每行两个整数L_iL
i
, R_iR
i
,中间用空格隔开,表示第ii次购物选购区间[L_i, R_i][L
i
,R
i
]的年糕。
1\leq n,m\leq 200000, 1\leq wi,vi\leq 1e6, 1\leq S\leq 1e12, 1\leq Li\leq Ri\leq n1≤n,m≤200000,1≤wi,vi≤1e6,1≤S≤1e12,1≤Li≤Ri≤n
Output
一个整数,表示所求最小值。
Sample Input 1
5 3 15
1 5
2 5
3 5
4 5
5 5
1 5
2 4
3 3
Sample Output 1
10
Hint
当WW为4时,三次购物的幸福值分别为20,5,020,5,0,年糕的幸福值为2525,此时与标准值SS相差为1010,这是最优情况.
Source
2019年集训队选拔赛
思路:前缀的差分和构成二分的判断条件,是洛谷上一道原题。
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cstdlib>
using namespace std;
const int maxn=200010;
int w[maxn],v[maxn],l[maxn],r[maxn];
long long p_n[maxn],p_v[maxn];
long long Y,s,sum;
int n,m;
long long mx=-1,mn=1e18;
long long Max(long long a,long long b)
{
return a>b?a:b;
}
long long Min(long long a,long long b)
{
return a<b?a:b;
}
bool check(int W)
{
Y=0,sum=0;
memset(p_n,0,sizeof(p_n));
memset(p_v,0,sizeof(p_v));
for(int i=1;i<=n;i++)
{
if(w[i]>=W) p_n[i]=p_n[i-1]+1,p_v[i]=p_v[i-1]+v[i];
else
p_n[i]=p_n[i-1],p_v[i]=p_v[i-1];
}
for(int i=1;i<=m;i++)
Y+=(p_n[r[i]]-p_n[l[i]-1])*(p_v[r[i]]-p_v[l[i]-1]);
sum=llabs(Y-s);
if(Y>s)
return true;
else
return false;
}
int main()
{
scanf("%d %d %lld",&n,&m,&s);
for(int i=1;i<=n;i++)
{
scanf(" %d %d",&w[i],&v[i]);
mx=Max(mx,w[i]);
mn=Min(mn,w[i]);
}
for(int i=1;i<=m;i++)
scanf(" %d %d",&l[i],&r[i]);
int left=mn-1,right=mx+2,mid;
long long ans=999999999999999;
while(left<=right)
{
mid=(left+right)>>1;
if(check(mid)) left=mid+1;
else right=mid-1;
if(sum<ans) ans=sum;
}
printf("%lld",ans);
return 0;
}