2022icpc亚洲区域赛(南京站)Problem D - 聊天程序

2022 i c p c 亚洲区域赛(南京站) P r o b l e m D − 聊天程序 \Huge{2022icpc亚洲区域赛(南京站)Problem D - 聊天程序} 2022icpc亚洲区域赛(南京站)ProblemD聊天程序

文章目录


题目链接:Problem - D - Codeforces

官方题解:D - 聊天程序 - SUA Wiki

题意

本题首先给出 n , k , m , c , d n,k,m,c,d n,k,m,c,d,然后给出 n n n个整数 a 1 , a 2 . . . a n a_1,a_2...a_n a1,a2...an

题目要求执行以下操作至少一次:

  • 选择一个长度恰好为 m m m的子数组 a a a,然后将长度也为 m m m的等差数组(首项为 c c c,公差为 d d d)依次加到对应的子数组 a a a上。

求至多一次操作之后,序列中第 k k k大的值最大可能是多少?

数据范围:

  • 1 ≤ k , m ≤ n ≤ 2 × 1 0 5 1\le k,m\le n \le 2\times 10^5 1k,mn2×105
  • 0 ≤ c , d ≤ 1 0 9 0\le c,d \le 10^9 0c,d109
  • 0 ≤ a i ≤ 1 0 9 0\le a_i\le 10^9 0ai109

思路

通过题意,我们可以发现题目要求的答案符合单调性,即如果答案 k k k符合要求,那么小于 k k k的数也必然符合要求(忽略能否构造出 k k k)。

因此,我们考虑使用二分答案。

那么,对于答案 k k k,当我们枚举小于 k k k的时候,check()函数必然是返回true,所以说使用二分答案最终枚举到的 k k k必定可以构造出。

但是,这道题目的难点就在于check()函数的判断,check()函数的时间复杂度必须保持在 O ( n ) O(n) O(n)的时间复杂度下可通过本题。

具体做法为:

  • 首先二分答案 m i d mid mid,将大于等于 m i d mid mid的数看成 1 1 1,小于 m i d mid mid的数看成 0 0 0。问题变为“判断能否通过至多一次操作,使序列中 1 1 1的数量大于等于 m i d mid mid”。
  • 接下来枚举操作位置,并计算进行操作后能否满足要求。考虑一个元素 a t ( a t < x ) a_t(a_t<x) at(at<x)容易发现,在操作范围从左往右移的过程中,当 a t a_t at第一次进入操作范围时,它会变成最大值,之后慢慢变小,最后又变回原来的值。因此每个数只会从 0 0 0变成 1 1 1 一次,再从 1 1 1变成 0 0 0一次。
  • 我们只要对每个元素找出这两次变化的位置,就能利用前缀和算出在每个位置进行操作对 1 1 1的数量的影响。

标程

#include<bits/stdc++.h>

using namespace std;

#define IOS ios::sync_with_stdio(false); cin.tie(nullptr), cout.tie(nullptr);
#define int long long 
#define ULL unsigned long long 
#define PII pair<int, int>
#define lowbit(x) (x & -x)
#define Mid ((l + r) >> 1)
#define ALL(x) x.begin(), x.end()
#define endl '\n'
#define fi first 
#define se second

const int INF = 0x7fffffff;
const int Mod = 1e9 + 7;
const int N = 2e5 + 10; 

int n, k, m, c, d;
vector<int> a, f;

bool check(int mid) {
    int cnt = 0;//记录不小于mid的个数
    for(int i = 1; i <= n; i ++ ) if(a[i] >= mid) cnt ++;

    if(cnt >= k) return true;//剪枝,个数符合直接范围true
    
    f.clear(); f.resize(n + 5);		//关键的f数组,用于记录前缀和

    for(int i = 1; i <= n; i ++ ) {
        if(a[i] >= mid) continue;    //只判断小于mid的数字

        int r = min(m - 1, i - 1);
        int mx = a[i] + c + d * r;  //当前数字最大能加多少

        if(mx < mid) continue;
        f[max(m, i)] ++;            //记录这个数字大于mid的区间的起点的坐标

        int mi = a[i] + c;          //当前数字最小能加多少
        if(mi >= mid) f[min(i + m, n + 1)] --;//若加最小也满足,则只有其不在区间m中时才去掉
        else {
            int t = mid - a[i] - c;
            int pos = (t + d - 1) / d - 1;
            f[min(n + 1, i + m - pos - 1)] --;
        }
    }
    for(int i = m; i <= n; i ++ ) {
        cnt += f[i];
        if(cnt >= k) return true;
    }
    return false;
}

void Solved() { 
    cin >> n >> k >> m >> c >> d;
    a.resize(n + 1);

    for(int i = 1; i <= n; i ++ ) {
        cin >> a[i];
    }

    int l = 0, r = 1e15, mid;//需要根据题目计算出答案可能的最大值
    while(l < r) {
        mid = l + r + 1 >> 1;
        if(check(mid)) l = mid;
        else r = mid - 1;
    }

    cout << l << endl;
}

signed main(void) {
    IOS

    int ALL = 1; 
    // cin >> ALL;
    while(ALL -- ) Solved();
    // cout << fixed;//强制以小数形式显示
    // cout << setprecision(n); //保留n位小数

    return 0;
}
  • 23
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值