LuoguP1314 聪明的质检员 【二分答案/前缀和】

美丽的题号预示着什么...

描述

小 T 是一名质量监督员,最近负责检验一批矿产的质量。这批矿产共有n个矿石,从1到n逐一编号,每个矿石都有自己的重量wi以及价值vi。检验矿产的流程是:
1、给定m个区间[Li,Ri];
2、选出一个参数W;
3、对于一个区间[Li,Ri],计算矿石在这个区间上的 检验值Yi

这批矿产的 检验结果Y 为各个区间的检验值之和 。即: Y1+Y2...+Ym
若这批矿产的 检验结果 与所给标准值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。 注意:不同区间可能重合或相互重叠。

输出格式

输出只有一行,包含一个整数,表示所求的最小值。

样例1

样例输入1

5 3 15
1 5
2 5
3 5
4 5
5 5
1 5
2 4
3 3

样例输出1

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 ≤ 10^6,0 < S ≤ 10^12,1 ≤ Li ≤ Ri ≤ n。

来源

NOIp2011提高组Day2第二题

这道题...题面读了好久(晦涩难懂

做背包做惯了于是以下及我的代码把数组v与w的意义调换了一下(v为重量,w为价值)

因为参数值W是不固定的,而且坐落在有序的一段区间上(1~max v[i] ),所以我们可以愉快地使用二分答案。

如果我们二分出的这个参数值使得Y较小,说明参数取得有些大,满足的值很少,我们向左移一移。

如果我们二分出的这个参数值使得Y较大,说明参数取得有些小,满足的值很多,我们向右移一移。

根据lyd老师的指引,我们愉快地写出了二分答案的代码

    int l=1,r=mx;
    while(l<r)
    {
        int mid=(l+r+1)>>1;
        Y=check(mid);
        if(Y<s) r=mid-1;
        else l=mid;
        ans=min(ans,abs(s-Y));
    }

接下来就是check函数的写法。

由于数据达到了1e6,我们肯定不能暴力计算区间和,前缀和坠吼了。

ll check(int x)
{
    ll tmp=0;
    for(int i=1;i<=n;i++)
    {
        sum[i]=sum[i-1];
        cnt[i]=cnt[i-1];
        if(v[i]>=x)
        {
            sum[i]+=w[i];
            cnt[i]++;
        }
    }
    for(int i=1;i<=m;i++)
     tmp+=(sum[q[i].second]-sum[q[i].first-1])*(cnt[q[i].second]-cnt[q[i].first-1]);
    return tmp;
}

于是我们就愉快地AC了。

code

 1 #include<cstdio>
 2 #include<algorithm>
 3 #include<utility>
 4 #define inf (1LL<<60)
 5 
 6 using namespace std;
 7 typedef long long ll;
 8 
 9 int n,m,mx;
10 int v[200009],w[200009];
11 ll ans=inf,s,Y,sum[200009],cnt[200009];
12 pair<int,int> q[200009];
13 /*ll check(int x)
14 {
15     ll tmp=0;
16     for(int i=1;i<=m;i++)
17     {
18         int pos=lower_bound(v+1,v+n+1,x)-v;
19         if(pos>q[i].second) continue;
20         int cnt=max(pos,q[i].first);
21         tmp+=(w[q[i].second]-w[cnt-1])*(q[i].second-cnt+1);
22     } 
23     return tmp;
24 }*/
25 ll check(int x)
26 {
27     ll tmp=0;
28     for(int i=1;i<=n;i++)
29     {
30         sum[i]=sum[i-1];
31         cnt[i]=cnt[i-1];
32         if(v[i]>=x)
33         {
34             sum[i]+=w[i];
35             cnt[i]++;
36         }
37     }
38     for(int i=1;i<=m;i++)
39      tmp+=(sum[q[i].second]-sum[q[i].first-1])*(cnt[q[i].second]-cnt[q[i].first-1]);
40     return tmp;
41 }
42 int main()
43 {
44     scanf("%d%d%lld",&n,&m,&s);
45     for(int i=1;i<=n;i++)
46      scanf("%d%d",&v[i],&w[i]),mx=max(mx,v[i]);
47     for(int i=1;i<=m;i++)
48      scanf("%d%d",&q[i].first,&q[i].second);
49 /*    sort(a+1,a+n+1,cmp);*/
50 /*    sum[1]=a[1].w;
51     for(int i=2;i<=n;i++)
52      sum[i]=sum[i-1]+a[i].w;*/
53     
54     int l=1,r=mx;
55     while(l<r)
56     {
57         int mid=(l+r+1)>>1;
58         Y=check(mid);
59         if(Y<s) r=mid-1;
60         else l=mid;
61         ans=min(ans,abs(s-Y));
62     }
63     printf("%lld",ans);
64     return 0;
65 }
View Code

几个注意事项

§ 虽说没有负数的情况,但是ans初值本身开始也要赋得很大,否则会输出0. (因为这调了很久qaq),ans是long long类型,怎么赋初值?hzwer大神给了一个不错的写法

#define inf (1LL<<60)

§ 开始想排一遍序,然后前缀和直接调用就好了。然而...并没有注意到还有区间下标的限制,还是老实做吧orz!

原文地址:https://www.cnblogs.com/nopartyfoucaodong/p/9329702.html