[luogu-1314]noip2011 day2-T2聪明的质监员 题解

题目传送门
题意解析:题目告诉了你n块矿石,并且每次选取一段矿石,给出了矿石的质量计算方式,让你求出每段质量之和(Y)与要求的质量和(S)的最小差(即求min{abs(Y-S)}),每次计算的方案是,对于一个选定的重量W,每段的质量=重量超过W的个数*重量超过W的价值之和。


My opinion:我一开始看到这题目,有一件事是很明显的,如果我们知道了W,那么我们可以快速计算出每一段的质量,(不过像我这种zz一开始认为需要用数据结构维护,比如线段树,但是后来突然发现自己犯蠢,其实O(n)线扫维护片段和就好了)。我们不能枚举W,这样显然要超时,那么我们自然想到了二分,但是因为这题答案是要求abs(Y-S),是一个山峰,所以我就去写了三分,于是完美爆炸(后来有dalao给我证明了二分也是可行了,而然我这种蒟蒻没发现),三分会有一些问题存在。
如图:
[luogu-1314]noip2011 day2-T2聪明的质监员 题解_第1张图片
就是这样,所以该怎么三分呢,我们可以不要去三分值,因为最优解的W一定存在于一种W=w[i](任意i)。所以我们可以去以w[i]去三分,这样就可以了。
总结:
1、输入。
2、三分(二分),记得三分的数组要排序+去重。
3、计算每种质量。
4、输出。


代码:

#include
#include
#include
#include
#include
#define rep(i,a,n) for (int i=a;i<=n;i++)
#define per(i,a,n) for (int i=a;i>=n;i--)
#define Clear(a,x) memset(a,x,sizeof(a))
#define ll long long
#define INF 2000000000
#define eps 1e-8
using namespace std;
ll read(){
    ll x=0,f=1; 
    char ch=getchar();
    while (ch<'0'||ch>'9') f=ch=='-'?-1:f,ch=getchar();
    while (ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
    return x*f;
}
const int maxn=200005;
int n,m;
ll S;
int w[maxn],v[maxn],a[maxn],l[maxn],r[maxn];
int L,R;
ll sum[maxn],c[maxn];
ll calc(int W){
    c[0]=0,sum[0]=0;
    rep(i,1,n){
        sum[i]=sum[i-1];
        c[i]=c[i-1];
        if (w[i]>=W) sum[i]+=(ll)v[i],c[i]++; 
    }
    ll Y=0;
    rep(i,1,m)
        Y+=(sum[r[i]]-sum[l[i]-1])*(c[r[i]]-c[l[i]-1]);
    return Y;
}
int main(){
    n=read(),m=read(),S=read();
    rep(i,1,n) w[i]=read(),v[i]=read(),a[i]=w[i];
    rep(i,1,m) l[i]=read(),r[i]=read();
    sort(a+1,a+n+1);
    R=unique(a+1,a+n+1)-a-1;
    L=0;
    ll ans=-1;
    while (L<=R){
        int mid1=(L+R)>>1,mid2=min(R,mid1+1);
        ll y1=calc(a[mid1]),y2=calc(a[mid2]);
        y1=abs(y1-S),y2=abs(y2-S);
        if (y11;
            else if (y1>y2) L=mid1+1;
                else {
                    ans=y1;
                    L=mid1+1;
                    R=mid2-1;
                }
    }
    printf("%lld\n",ans);
    return 0;
}

你可能感兴趣的:(二分,洛谷,历届noip)