[JXOI2017]加法

题目

题目描述

可怜有一个长度为 n 的正整数序列 A,但是她觉得 A 中的数字太小了,这让她很不开心。

于是她选择了 m 个区间 [li, ri] 和两个正整数 a, k。她打算从这 m 个区间里选出恰好 k 个区间,并对每个区间执行一次区间加 a 的操作。(每个区间最多只能选择一次。)

对区间 [l, r] 进行一次加 a 操作可以定义为对于所有 i ∈ [l, r],将 Ai 变成 Ai + a。现在可怜想要知道怎么选择区间才能让操作后的序列的最小值尽可能的大,即最大化min Ai

输入格式

第一行输入一个整数表示数据组数。

对于每组数据第一行输入四个整数 n, m, k, a。

第二行输入 n 个整数描述序列 A。

接下来 m 行每行两个整数 li, ri 描述每一个区间。数据保证所有区间两两不同。

输出格式

对于每组数据输出一个整数表示操作后序列最小值的最大值。

输入输出样例

输入 #1复制

1 
3 3 2 1
1 3 2
1 1
1 3
3 3

输出 #1复制

3

说明/提示

选择给区间 [1, 1] 和 [1, 3] 加 1。

对于100%的数据,保证1\leq \sum n, \sum m \leq 200000,1≤∑n,∑m≤200000, 1\leq T\leq 200000, 1 ≤ k ≤ m, 1 ≤ a ≤ 100, 1 ≤ A_i ≤ 10^81≤T≤200000,1≤k≤m,1≤a≤100,1≤Ai​≤108

题解&分析

首先是二分答案很容易想到,那么怎么写check呢?

贪心是最先想到的,从左往右扫,对于每一个比二分的答案ans小的点进行判断,因为前面的点已经满足,所以就选择区间长度最长且包含当前点的区间加上来

但是这只是思想,怎么实现呢?

用扫描线的思想进行优化。

把每个区间的端点与每个点都存进优先队列中,从走往右,那么就有m*2+n个点

然后进行check x,我们只需要定义一个变量o,对于扫到了区间左端点,把这个区间加进另一个优先队列,如果是右端点 , 那么 o -= a 对于是非区间端点的点,那么就用上面的方法,还需要加上的权值 x - o - A[i] ,然后去对存区间的优先队列操作,将其按右端点从大到小排序,如果可以加,就加上,则o += A[i]

代码

#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <vector>
#include <cmath>
#include <queue>
#include <stack>
using namespace std;
#define ll long long
const int MAXN = 200003;
ll A ;
int n , m , k;
struct node{
    int flag , num ;
    ll x;
    node(){}
    node( int F , int N , int X ){
        flag = F;
        num = N;
        x = X;
    }
    friend bool operator < ( node a , node b ){
        return a.num + ( ( a.flag * 1.0 )  / 3.0 ) < b.num + ( ( b.flag * 1.0 )  / 3.0 );
    }
}s[MAXN*4];
int cnt;
int vis[MAXN];
int R[MAXN];
struct edge{
    int x;
    edge(){}
    edge( int X ){
        x = X;
    }
    friend bool operator < ( edge a , edge b ){
        return R[a.x] < R[b.x];
    }
};
bool check( ll mu ){
    priority_queue<edge>q;
    while( !q.empty() )
        q.pop();
    int tot = 0;
    memset( vis , 0, sizeof( vis ) );
    ll o = 0;
    for( int i = 1 ; i <= cnt ; i ++ ){
        if( s[i].flag == 0 ){
            q.push( edge( s[i].x ) );
        }
        else if( s[i].flag == 2 ){
            o -= vis[s[i].x] * A;

        }
        else{
            ll ch = ( mu - s[i].x - o );
            if( ch <= 0 ) continue;
            int need = ch / A;
            if( A * need < ch ) need ++;
            if( k - tot < need ) return 0;
            while( !q.empty() && need > 0 ){
                int v = q.top().x;
                q.pop();
                if( R[v] < s[i].num ){
                    return 0;
                }
                vis[v] = 1 , tot ++ , need --;
                o += A;
            }
            if( need > 0 )
                return 0;
        }
    }
    return 1;
}
int main(){
    int t;
    scanf( "%d" , &t );
    while( t -- ){
        scanf( "%d%d%d%lld" , &n , &m , &k , &A );
        ll l = 0 , r = 0 , ans;
        cnt = 0 ;
        memset( vis , 0 , sizeof( vis ) );
        for( int i = 1 ; i <= n ; i ++ ){
            ll x;
            scanf( "%lld" , &x );
            s[++cnt] = node( 1 , i , x );
            r = max( r , x );
            if( !l )
                l = x;
            else
            l = min( l , x );
        }
        for( int i = 1 ; i <= m ; i ++ ){
            int x;
            scanf( "%d" , &x );
            s[++cnt] = node( 0 , x , i );
            scanf( "%d" , &x );
            s[++cnt] = node( 2 , x , i );
            R[i] = x;
        }
        sort( s + 1 , s + cnt + 1 );
        r = r + ( 1ll * k * A );
        while( l <= r ){
            ll mid = ( l + r ) / 2;
            if( check( mid ) )
                ans = mid , l = mid + 1;
            else
                r = mid - 1 ;
        }
        printf( "%lld\n" , ans );
    }
    return 0;
}
发布了68 篇原创文章 · 获赞 7 · 访问量 3840

猜你喜欢

转载自blog.csdn.net/weixin_43823476/article/details/100734131