[题解] 聪明的质监员 二分+前缀和
题目链接
对于这种>=x求x的问题,显然一秒钟就能想到二分。
∑
j
=
l
i
r
i
[
w
j
≥
W
]
×
∑
j
=
l
i
r
i
[
w
j
≥
W
]
v
j
\sum_{j = l_i}^{r_i}\left [ w_j \ge W \right ] \times \sum_{j = l_i}^{r_i}\left [ w_j \ge W \right ]v_j
j=li∑ri[wj≥W]×j=li∑ri[wj≥W]vj
对于这个诡异的式子,可以看出用前缀和优化。
对于每个w,维护第一个式子和第二个式子的前缀和,然后计算y的总和。
时间复杂度为
O
(
n
+
m
)
O\left(n+m\right)
O(n+m)。
然后只需要二分分别找一下使
y
(
w
)
≥
s
y(w) \ge s
y(w)≥s和使
y
(
w
)
≤
s
y(w) \le s
y(w)≤s的w就可以了。
总的时间复杂度是
O
(
(
n
+
m
)
log
(
max
w
i
)
)
O\left( (n+m)\log \left(\max w_i \right)\right)
O((n+m)log(maxwi))。
注意
\quad
每个二分都需要特判!
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<cmath>
#include<algorithm>
#include<vector>
#include<map>
#include<queue>
#include<stack>
#define ll long long
#define pii pair<int,int>
using namespace std;
const double eps = 1e-10;
const double pi = acos(-1.0);
const int maxn = 2e5 + 10;
int n,m;
int l[maxn], r[maxn];
ll w[maxn] ,v[maxn];
ll sum1[maxn],sum2[maxn];
ll s;
ll cal(int x){//计算x的总的y值
ll ans = 0;
memset(sum1,0,sizeof(sum1));
memset(sum2,0,sizeof(sum2));
for(int i = 1; i <= n; i++){//对于每个x,单独计算出两个前缀和
sum1[i] = sum1[i-1];
sum2[i] = sum2[i-1];
if(w[i] >= x){
sum1[i]++;
sum2[i] += v[i];
}
}
for(int i = 1; i <= m; i++) ans += (sum1[r[i]] - sum1[l[i]-1])*(sum2[r[i]] - sum2[l[i]-1]);//计算y的和
return ans;
}
void solve(){
scanf("%d%d%lld",&n,&m,&s);
int maxw = 0;
for(int i = 1; i <= n; i++){
scanf("%lld%lld",&w[i],&v[i]);
maxw = max(maxw,(int)w[i]);
}
for(int i = 1; i <= m; i++){
scanf("%d%d",&l[i],&r[i]);
}
int l1 = 0, r1 = maxw;
while(l1 < r1){//找使y>=s的最小数
int mid = l1 + r1 + 1>> 1;
if(cal(mid) >= s) l1 = mid;
else r1 = mid - 1;
}
ll ans1 = cal(l1) - s;
l1 = 0, r1 = maxw;
while(l1 < r1){//找使y<=s的最大数
int mid = l1 + r1 >> 1;
if(cal(mid) <= s) r1 = mid;
else l1 = mid + 1;
}
ll ans2 = s - cal(l1);
//注意,可能ans1<0 或者 ans2 <0 (没有该区间),需要特判。
if(ans1 >= 0 && ans2 >= 0) printf("%lld\n",min(ans1,ans2));
else printf("%lld\n", ans1 >= 0 ? ans1 : ans2);
}
int main()
{
solve();
return 0;
}