思路 (二分,前缀和)
O
(
n
l
o
g
W
)
O(nlogW)
O(nlogW) 观察每个区间的值
Y
i
=
(
∑
j
1
)
∗
(
∑
j
v
j
)
,
j
∈
[
L
i
,
R
i
]
Y_i=(\sum\limits_{j}1)∗(\sum\limits_j v_j),j∈[L_i,R_i]
Yi=(j∑1)∗(j∑vj),j∈[Li,Ri]且
w
j
≥
W
w_j≥W
wj≥W 当
W
W
W 增大时,区间
[
L
i
,
R
i
]
[L_i,R_i]
[Li,Ri] 中满足要求的
(
w
i
,
v
i
)
(w_i,v_i)
(wi,vi) 会减少,同时所有
v
i
≥
0
v_i≥0
vi≥0,因此
Y
i
Y_i
Yi 的值也会减少。 由于
Y
=
∑
i
Y
i
Y=\sum\limits_{i}Y_i
Y=i∑Yi,所以
Y
Y
Y 随
W
W
W 单调递减。 因此我们可以二分出距离
S
S
S 最近的值。 剩下的问题是当
W
W
W 确定之后,我们如何快速求出每个
Y
i
Y_i
Yi。 这里可以预处理出所有满足
w
i
≥
W
w_i≥W
wi≥W 的元素的前缀和,之后可以
O
(
1
)
O(1)
O(1) 时间求出每个区间中所有
v
i
v_i
vi 的和,以及满足要求的元素个数。
代码
#include<bits/stdc++.h>usingnamespace std;constint maxn=2e5+10;typedeflonglong ll;int n,m;
ll S;int l[maxn],r[maxn];int w[maxn],v[maxn];
ll sum[maxn];int cnt[maxn];
ll getres(int x){
ll res =0;for(int i =1; i <= n; i++){if(w[i]>= x){
cnt[i]= cnt[i -1]+1;
sum[i]= sum[i -1]+ v[i];}else{
cnt[i]= cnt[i -1];
sum[i]= sum[i -1];}}for(int i =1; i <= m; i++){
res +=(cnt[r[i]]- cnt[l[i]-1])*(sum[r[i]]- sum[l[i]-1]);}return res;}intmain(){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",&l[i],&r[i]);}int L =0, R =1e6;int ans =0;while(L <= R){int mid = L + R >>1;if(getres(mid)>= S){
ans = mid;
L = mid +1;}else R = mid -1;}printf("%lld\n",min(abs(S -getres(ans)),abs(S -getres(ans +1))));return0;}