题解 P1314 【聪明的质监员】

题目链接

这种质监员迟早要下岗,而且这题数据真的水,试了两个错误做法都给放过去了……

Solution [NOIP2011]聪明的质监员

题目大意:给定\(n\)个矿石,每个矿石有重量\(w\)和价值\(v\),给定\(m\)个区间\([L_i,R_i]\),定义一个区间的贡献\(Y_i = \sum_j1\times\sum_j v_j \quad j \in [L_i,R_i]\;and\;w_j >= W\),求一个最优的参数\(W\)使得\(\sum Y\)尽量接近所给定的\(s\)

分析:首先这题要想明白\(Y\)的值是单调的,因为\(W\)越小,我们可以选择的矿石就越多,所以\(Y\)就越大

然后我们可以往二分上想,首先我们将输入所给的\(w\)去重并且按升序排序,然后此时我们就可以找到一个最小的\(w_i\)使得\(\sum Y \leq s\),那么当\(W\)\(w_i-1\)的时候\(\sum Y > s\)(边界情况,即\(i = 1\)的时候不成立),那么此时我们在\(w_i\)\(w_{i-1}\)中取最优解就好了

但是要注意一个问题,我们要在\(w\)集合中插入一个\(INF\)值,这样才可以代表所有矿石都不选的情况(所以这题数据真的水)

还有一个问题(脑补老爹语音),怎么算\(\sum Y\),如果暴力的话复杂度\(nm\)直接升天(一开始写的主席树,写到一半想抽自己一巴掌),我们惊喜地发现无论是满足条件的矿石个数,还是\(\sum v\)都是可以用前缀和来优化的,所以单次二分的复杂度就可以做到\(n + m\)级别了

所以复杂度\(O((n + m)log\;card(w))\)

\(cz\)是真的神仙,不打表\(56ms\)是怎么跑出来的 Orz

// luogu-judger-enable-o2
#include <algorithm>
#include <cstdio>
#include <cctype>
using namespace std;
typedef long long ll;
const int maxn = 2e5 + 100;
inline ll read(){
    ll x = 0;char c = getchar();
    while(!isdigit(c))c = getchar();
    while(isdigit(c))x = x * 10 + c - '0',c = getchar();
    return x;
}
struct Seg{int l,r;}seg[maxn];
struct Stone{int w,v;}stone[maxn];
ll sum_cnt[maxn],sum_v[maxn],w[maxn],s;
int n,m,l = 0x7fffffff,r = -0x7fffffff,tmp;
ll solve(int w){
    ll res = 0;
    for(int i = 1;i <= n;i++)
        if(stone[i].w >= w)sum_cnt[i] = sum_cnt[i - 1] + 1,sum_v[i] = sum_v[i - 1] + stone[i].v;
        else sum_cnt[i] = sum_cnt[i - 1],sum_v[i] = sum_v[i - 1];
    for(int i = 1;i <= m;i++)
        res += (sum_cnt[seg[i].r] - sum_cnt[seg[i].l - 1]) * (sum_v[seg[i].r] - sum_v[seg[i].l - 1]);
    return res;
}
int main(){
    n = read(),m = read(),s = read();
    for(int i = 1;i <= n;i++)
        w[i] = stone[i].w = read(),stone[i].v = read();
    for(int i = 1;i <= m;i++)
        seg[i].l = read(),seg[i].r = read();
    w[n + 1] = 0x7fffffff;//插入一个INF代表所有矿石都不选
    sort(w + 1,w + 2 + n);
    int siz = unique(w + 1,w + 2 + n) - w - 1;//去重 + 排序
    l = 1,r = siz;
    while(l <= r){
        int mid = (l + r) >> 1;
        if(solve(w[mid]) <= s)tmp = mid,r = mid - 1;
        else l = mid + 1;
    }
    ll ans = min(abs(solve(w[tmp]) - s),abs(solve(w[tmp - 1]) - s));//取最优
    return printf("%lld\n",ans),0;
}

猜你喜欢

转载自www.cnblogs.com/colazcy/p/11515137.html