NOIP2011 聪明的质检员 - 前缀和 - 二分

这题需要操作w,使得y与s尽量接近
那么考虑到y可以比s小一点也可以大一点,可以分开考虑,若y < s 则尝试把y弄大一点,
这样就可以二分w,使y越来越接近s,并且实时记录最优解,也不用管最后w是多少
难点在如何快速算检验值
考虑多个区间叠加浪费了时间,有两种可能优化:
1.把重叠区间预先求出来…太复杂,写了估计也不对
2.其实就是对区间一些满足要求的点求和,那么不考虑不满足的点,区间求和,或者是说各种求和,都可以用前缀和变成O(1)来做(类似还有树上前缀和 f[i]表示从根到i的和)

如果说没想到前缀和,是因为没能把当前问题抽象提升出来

#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
using namespace std;
#define debug(x) cerr << #x << "=" << x << endl;
const int MAXN = 200000 + 10;
int n,m,tot,wmax,w[MAXN],v[MAXN];
typedef long long ll;
ll s, ans;
int fs1[MAXN];
ll fs2[MAXN];
struct sect{
    int l,r;
}sec[MAXN];
ll ab(ll x) {
    if(x < 0) return -x;
    return x;
}
ll calc(int wi) {
    ll y = 0;
    memset(fs1, 0, sizeof(fs1));
    memset(fs2, 0, sizeof(fs2));
    for(int i=1; i<=n; i++) {
        if(w[i] >= wi) {
            fs1[i] = 1;
            fs2[i] = v[i];
        }
        fs1[i] += fs1[i-1];
        fs2[i] += fs2[i-1];
    }
    for(int i=1; i<=m; i++) {
        int l = sec[i].l, r = sec[i].r;
        int nj = fs1[r] - fs1[l-1];
        ll ns = fs2[r] - fs2[l-1];
        y += ns * nj;
    }
    return y;
}
int main() {
    cin >> n >> m >> s;
    for(int i=1; i<=n; i++) {
        scanf("%d %d", &w[i], &v[i]);
        wmax = max(wmax, w[i]);
    }
    for(int i=1; i<=m; i++) {
        scanf("%d %d", &sec[i].l, &sec[i].r);       
    }
    int l = 0, r = wmax;
    ans = ab(s - calc(wmax));//这个地方超易错 注意答案是s-y不是y 我第一回写成了ans = y 所以静态差错很重要!
    while(l <= r) {
        int mid = l+r>>1;
        ll y = calc(mid);
        ll now = ab(s-y);
        if(now < ans) {
            ans = now;
        }
        if(y > s) {
            l = mid+1;
        } else {
            r = mid-1;
        }
    }
    cout << ans;
    return 0;
}

猜你喜欢

转载自blog.csdn.net/Fantasy_World/article/details/81474440