P1314 [NOIP2011 提高组] 聪明的质监员

Problem

\(n\)个矿石,每个矿石用二元组\((w_i,v_i)\)表示。有\(m\)个区间,第\(i\)个区间为\([l_i,r_i]\),定义\(y_i = \sum_{j = l_i} ^ {r_i} [w_j \ge W] \cdot \sum_{j = l_i} ^ {r_i} [w_j \ge W] \cdot v_j\),其中\(W\)是一个自定的参数。定义\(y = \sum_{i = 1} ^ m y_i\)。给你一个数\(s\),要你选择一个合适的参数\(W\),使得\(|y - s|\)最小。
\(1 \le n,m \le 200000,0 < w_i,v_i \le 10^6,0 < s \le 10^{12}\)

Solution

Thinking 1

不难发现\(W\)越大,\(y\)则越小。考虑二分答案。
但是check爆算的话是\(\mathcal{O}(nm)\),显然过不去。
我们发现,对于一个固定的\(W\),只需要算出\(\sum_{j = l_i} ^ {r_i} [w_j \ge W]\)\(\sum_{j = l_i} ^ {r_i} [w_j \ge W] \cdot v_j\),而这些都可以前缀和。

# include <bits/stdc++.h>
using namespace std;
# define int long long 
const int N = 200005;
int n,m,s;
struct node {int w,v; node() {} node(int _w,int _v) : w(_w),v(_v) {}}a[N];
struct qujian {int l,r; qujian() {} qujian(int ll,int rr) : l(ll),r(rr) {}}qu[N];
int q1[N],q2[N];
int check(int mid)
{
    memset(q1,0,sizeof(q1)),memset(q2,0,sizeof(q2));
    for(int i = 1; i <= n; i++)
    {
        if(a[i].w >= mid) q1[i] = q1[i - 1] + 1,q2[i] = q2[i - 1] + a[i].v;
        else q1[i] = q1[i - 1],q2[i] = q2[i - 1];
    }
    int y = 0;
    for(int i = 1; i <= m; i++) y = y + (q1[qu[i].r] - q1[qu[i].l - 1]) * (q2[qu[i].r] - q2[qu[i].l - 1]);
    return y;
}
signed main(void)
{
    scanf("%lld%lld%lld",&n,&m,&s);
    for(int i = 1; i <= n; i++) {scanf("%lld%lld",&a[i].w,&a[i].v);}
    for(int i = 1; i <= m; i++) {scanf("%lld%lld",&qu[i].l,&qu[i].r);}
    int l = 1e12,r = 0; for(int i = 1; i <= n; i++) l = min(l,a[i].w),r = max(r,a[i].w);
    l = 0;
    int ans = 1e12;
    while(l <= r)
    {
        int mid = (l + r) >> 1; int _mid = check(mid);
        if(_mid > s) l = mid + 1;
        else r = mid - 1;
        ans = min(ans,abs(_mid - s));
        // printf("mid = %lld,_mid = %lld,abs = %lld,ans = %lld\n",mid,_mid,abs(_mid - s),ans);
    }
    printf("%lld\n",ans);
    return 0;
}
posted @ 2021-07-18 20:03  luyiming123  阅读(50)  评论(0编辑  收藏  举报