天天看點

[NOIP2011][二分]聰明的質監員

題目描述:

題目背景: NOIP2011 DAY2 試題 2 。

小T是一名品質監督員,最近負責檢驗一批礦産的品質。這批礦産共有 n 個礦石,從 1 到 n 逐一編号,每個礦石都有自己的重量 wi 以及價值 vi。檢驗礦産的流程是:

1、給定 m 個區間[Li,Ri];

2、選出一個參數 W ;

3、對于一個區間[Li,Ri],計算礦石在這個區間上的檢驗值 Yi:

[NOIP2011][二分]聰明的質監員

這批礦産的檢驗結果Y 為各個區間的檢驗值之和。即:

[NOIP2011][二分]聰明的質監員

若這批礦産的檢驗結果與所給标準值 S 相差太多,就需要再去檢驗另一批礦産。小 T 不想費時間去檢驗另一批礦産,是以他想通過調整參數 W 的值,讓檢驗結果盡可能的靠近标準值 S ,即使得 S-Y 的絕對值最小。請你幫忙求出這個最小值。

輸入格式:

第一行包含三個整數 n,m,S,分别表示礦石的個數、區間的個數和标準值。

接下來的 n 行,每行 2 個整數,中間用空格隔開,第 i+1 行表示 i 号礦石的重量 wi 和價值 vi 。

接下來的 m 行,表示區間,每行 2 個整數,中間用空格隔開,第 i+n+1 行表示區間 [Li,Ri] 的兩個端點 Li 和 Ri。注意:不同區間可能重合或互相重疊。

輸出格式:

輸出隻有一行,包含一個整數,表示所求的最小值。

樣例輸入:

5 3 15

1 5

2 5

3 5

4 5

5 5

1 5

2 4

3 3

樣例輸出:

10

備注:

樣例說明:當 W 選 4 的時候,三個區間上檢驗值分别為 20、5、0,這批礦産的檢驗結果為 25,此時與标準值 S 相差最小為 10 。

資料範圍:

對于 10% 的資料,有 1≤n,m≤10;

對于 30% 的資料,有 1≤n,m≤500;

對于 50% 的資料,有 1≤n,m≤5,000;

對于 70% 的資料,有 1≤n,m≤10,000;

對于 100% 的資料,有 1≤n,m≤200,000;0<wi,vi≤106;0<S≤1012;1≤Li≤Ri≤n。

題目分析:

二分法。表示看不懂第一個求和,後來問了别人才知道是滿足要求的個數。就直接二分w,顯然w越大,計算出來的y越小。如果計算出來的y大于了s,就把w變大,反之,把w變小。

附代碼:

#include<iostream>
#include<cstring>
#include<string>
#include<cstdlib>
#include<cstdio>
#include<cmath>
#include<ctime>
#include<set>
#include<queue>
#include<map>
#include<map>
#include<iomanip>
#include<algorithm>
using namespace std;

const int maxn=+;
int n,m,num[maxn],w[maxn],v[maxn],l[maxn],r[maxn];
long long sum[maxn],ans,s;

int readint()
{
    char ch;int i=,f=;
    for(ch=getchar();(ch<'0'||ch>'9')&&ch!='-';ch=getchar());
    if(ch=='-') {ch=getchar();f=-;}
    for(;ch>='0'&&ch<='9';ch=getchar()) i=(i<<)+(i<<)+ch-'0';
    return i*f;
}

long long check(int x)
{
    memset(num,,sizeof(num));
    memset(sum,,sizeof(sum));
    ans=;
    for(int i=;i<=n;i++)
    {
        num[i]=num[i-];
        if(w[i]>=x) num[i]++;
        sum[i]=sum[i-];
        if(w[i]>=x) sum[i]+=v[i];
    }
    for(int i=;i<=m;i++)
        ans+=((num[r[i]]-num[l[i]-])*(sum[r[i]]-sum[l[i]-]));
    return ans;
}

long long half()
{
    int l=,r=,mid;
    long long ret,ret1,ret2,ret3;
    while(l<=r)
    {
        mid=(l+r)>>;
        ret=check(mid);
        if(ret==s) break;
        if(ret>s)
            l=mid+;
        else
            r=mid-;
    }
    if(ret==s) return ret;
    ret1=check(l);ret2=check(r);
    if(abs(ret1-s)<=abs(ret2-s)) return ret1;//因為最後l,r會是一大一小,但誰離s近并不确定,需要判斷。
    if(abs(ret2-s)<=abs(ret1-s)) return ret2;
}

int main()
{
    //freopen("lx.in","r",stdin);

    n=readint();m=readint();scanf("%I64d",&s);
    for(int i=;i<=n;i++)
    {
        w[i]=readint();
        v[i]=readint();
    }
    for(int i=;i<=m;i++)
    {
        l[i]=readint();
        r[i]=readint();
    }
    printf("%I64d",abs(half()-s));

    return ;
}