LGOI P1314 聪明的质监员

原题戳这里

题目描述

小T 是一名质量监督员,最近负责检验一批矿产的质量。这批矿产共有 \(n\) 个矿石,从 \(1\)\(n\) 逐一编号,每个矿石都有自己的重量\(w_i\)以及价值\(v_i\) 。检验矿产的流程是:

  1. 给定m个区间\(L_i,R_i\)

  2. 选出一个参数\(W\)

  3. 对于一个区间\([L_i,R_i]\),计算矿石在这个区间上的检验值Y i

\[ Y_i= ( \sum_j 1)(\sum_j {v_j}),\quad j \in [L_i,R_i] ,\quad w_j \geq W \]

其中,\(j\)是矿石的编号。

这批矿产的检验结果\(Y\) 为各个区间的检验值之和。即:\(Y_1+Y_2...+Y_m\)

若这批矿产的检验结果与所给标准值\(S\) 相差太多,就需要再去检验另一批矿产。小T不想费时间去检验另一批矿产,所以他想通过调整参数\(W\) 的值,让检验结果尽可能的靠近标准值\(S\),即使得\(S-Y\) 的绝对值最小。请你帮忙求出这个最小值。

输入格式

第一行包含三个整数\(n,m\),分别表示矿石的个数、区间的个数和标准值。

接下来的\(n\)行,每行\(2\)个整数,中间用空格隔开,第\(i+1\)行表示\(i\)号矿石的重量\(w_i\)和价值\(v_i\)

接下来的\(m\) 行,表示区间,每行\(2\) 个整数,中间用空格隔开,第\(i+n+1\)行表示区间\([L_i,R_i]\)的两个端点\(L_i\)\(R_i\)。注意:不同区间可能重合或相互重叠。

输出格式

一个整数,表示所求的最小值。

输入输出样例

输入 #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 < w_i,v_i≤10^6,0 < S≤10^{12},1 ≤L_i ≤R_i ≤n\)



問題の解

很明显这是一个二分的板子,想都没想就TLE了

感觉这个题目的背景在任何程度上说都很奇怪啊

首先考虑函数\(Y(W_{cur})\),直觉上告诉我们\(Y(W_{cur})\)具有单调性。证明懒得写。 其实是不会写

\[ Y(W_{cur})=\sum_i^m ( ( \sum_j 1) (\sum_j {v_i})),\quad j \in [L_i,R_i] ,\quad w_j \geq W_{cur} \]

接下来就是要找距离\(Y(W_{cur})-S\)零点最近的函数值。因为\(Y(W_{cur})\)是单调的,所以直接上二分。

\(1^{\circ} \quad\)\(Y(W_{cur})-S \geq 0\),\(W_{cur}\)偏小,将其调大;

\(2^{\circ} \quad\)\(Y(W_{cur})-S < 0\), \(W_{cur}\)偏大,将其调小;

↓然后随便打了一个\(Y(W_{cur})\)的函数,就爆炸了。甚至一开始这个暴力写法的下标还写错了,甚至还在这里卡了好久

inline ll FY1(ll curW)
{
    ll Y=0,cnt_j=0,sumv_of_range=0;
    for(int i=1;i<=m;i++)
    {
        cnt_j=0;
        sumv_of_range=0;
        for(int j=L[i];j<=R[i];j++)
        {
            if(w[j]>=curW)
            {
                cnt_j++;
                sumv_of_range+=v[j];
            }
        }
        Y+=sumv_of_range*cnt_j;
    }
    return Y;
}

这里优化的想法也不难,两个前缀数组\(pre_n[],pre_v[]\)即可。

但是没办法,每次调用\(Y(W_{cur})\)\(pre_n[],pre_v[]\)都要重新计算。但是速度比原来还是快很多的。

详见代码。

コード:

#include<bits/stdc++.h>
using namespace std;
#define ll long long 
inline int read()
{
    char c=getchar();int x=0;
    for(;!isdigit(c);c=getchar());
    for(;isdigit(c);c=getchar())
        x=x*10+c-'0';
    return x;
}
const int N=200010;
const ll LLINF=0x3f3f3f3f3f3f3f3f;
int v[N],w[N],L[N],R[N];
ll n,m,S;
ll pre_n[N],pre_v[N];

// inline ll FY1(ll curW)
// {
//     ll Y=0,cnt_j=0,sumv_of_range=0;
//     for(int i=1;i<=m;i++)
//     {
//         cnt_j=0;
//         sumv_of_range=0;
//         for(int j=L[i];j<=R[i];j++)
//         {
//             if(w[j]>=curW)
//             {
//                 cnt_j++;
//                 sumv_of_range+=v[j];
//             }
//         }
//         Y+=sumv_of_range*cnt_j;
//     }
//     return Y;
// }

inline ll FY(int curW)
{
    ll Y=0;
    memset(pre_n,0,sizeof(pre_n));
    memset(pre_v,0,sizeof(pre_v));
    for(int i=1;i<=n;i++)
    {
        if(w[i]>=curW)
        {
            pre_n[i]=pre_n[i-1]+1;
            pre_v[i]=pre_v[i-1]+v[i];
        }
        else
        {
            pre_n[i]=pre_n[i-1];
            pre_v[i]=pre_v[i-1];
        }
    }
    for(int i=1;i<=m;i++)
        Y+=(pre_n[R[i]]-pre_n[L[i]-1])*(pre_v[R[i]]-pre_v[L[i]-1]);
    return Y;
}

int main()
{
    //freopen("C:\\Users\\Administrator\\Downloads\\testdata (2).in","r",stdin);
    ll minw=LLINF,maxw=0;

    cin>>n>>m>>S;
    for(int i=1;i<=n;i++)
    {
        w[i]=read();
        v[i]=read();
        if(maxw<w[i])maxw=w[i];
        if(minw>w[i])minw=w[i];
    }
    for(int i=1;i<=m;i++)
    {
        L[i]=read();
        R[i]=read();
    }
    ll l=minw-1,r=maxw+2,mid,ans=LLINF;
    while(l<=r)
    {
        mid=((l+r)>>1);
        ll Y=FY(mid);
        if(Y>=S)l=mid+1;
        else r=mid-1;
        ans=min(ans,llabs(S-Y));
    }
    printf("%lld",ans);

    return 0;
}

猜你喜欢

转载自www.cnblogs.com/kion/p/11823449.html