集合操作题解+小结

csdn
题面

1.前言

我考试时把全部时间都交给这道题(T3)了,结果还没调出来,最后发现 自己是傻逼 自己又出现了傻逼错误,我真的是傻逼。为了警戒自己注意细节,于是写下了这篇题解

2.题解

现将 a a a 数组排序,可以考虑二分。二分什么呢?答曰:二分答案。

当然,不是集合中的每个元素都要二分答案,我们只二分原来还没有开始操作时最大的元素( a [ n ] a[n] a[n])最后的大小。

我们假设 a [ n ] a[n] a[n] 最后的大小排名第 i d x idx idx,不妨假设原 [ i d x , n ) [idx, n) [idx,n) 的元素最后的排名分布在 ( i d x , n ] (idx, n] (idx,n]

容易证明,当 a [ n ] a[n] a[n] 最后的大小越小,需要操作的次数越多。

c h e c k check check 函数就很好写了

bool check (LL x) {
    LL temp = a[n] - x * p;//最后a[n]的大小
    LL sum = 0;//统计需要操作多少次
    for (int i = 1; i <= n; i++) {
        if (a[i] <= temp) continue;//排名在 [idx, n]
        sum += (a[i] - temp) / p;
    }
    return sum >= k;//当操作次数大于k时满足要求 
}

但还没完,我们还需要对它进行一定的调整,因为 a [ i ] ( i ∈ [ i d x , n ) ) a[i] (i \in [idx, n)) a[i](i[idx,n)) 最后的大小不一定大于 a [ n ] a[n] a[n] 最后的大小。于是我们让操作次数为最小的大于 k k k 的值,最后再向前回溯。容易证明回溯的次数不超过 O ( n ) O(n) O(n)

一些细节就写在注释里面了。

参考代码(细节很多)

#include <map>
#include <queue>
#include <cstdio>
#include <vector>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;

#define int long long
#define LL long long
template <typename T>
void read (T &x) {
    x = 0; T f = 1;
    char ch = getchar ();
    while (ch < '0' || ch > '9') {
        if (ch == '-') f = -1;
        ch = getchar ();
    }
    while (ch >= '0' && ch <= '9') {
        x = (x << 1) + (x << 3) + ch - '0';
        ch = getchar ();
    }
    x *= f;
}
template <typename T>
void write (T x) {
    if (x < 0) {
        putchar ('-');
        x = -x;
    }
    if (x < 10) {
        putchar (x + '0');
        return;
    }
    write (x / 10);
    putchar (x % 10 + '0');
}
template <typename T> T Abs (T x) { return x > 0 ? x : -x; }

const int Maxn = 1e6;
const LL Maxr = 1e18;

int n;
LL k, p;
LL a[Maxn + 5], tema[Maxn + 5];
//tema用于存储原数组 

bool check (LL x) {
    LL temp = a[n] - x * p;//最后a[n]的大小
    LL sum = 0;//统计需要操作多少次
    for (int i = 1; i <= n; i++) {
        if (a[i] <= temp) continue;//排名在 [idx, n]
        sum += (a[i] - temp) / p;
    }
    return sum >= k;//当操作次数大于k时满足要求 
}
struct Node {
    int idx;
    LL val;
    Node () {}
    Node (int IDX, LL VAL) {
        idx = IDX;
        val = VAL;
    }
};
bool operator < (Node x, Node y) {
    return x.val > y.val;
}
priority_queue <Node> q;
void solve (LL x) {
    LL temp = a[n] - x * p;//原a[n]最后的大小 
    LL sum = 0;//需要的操作次数 
    for (int i = 1; i <= n; i++) {
        if (a[i] < temp) continue;
		//元素的最后排名在 [idx, n) 里我们才会统计操作次数并且才可能会回溯 
        sum += (a[i] - temp) / p;
        a[i] -= (a[i] - temp) / p * p;
        q.push (Node (i, a[i]));
    }
    for (int i = 1; i <= sum - k; i++) {
        Node tem = q.top (); q.pop ();
        while (a[tem.idx] + p > tema[tem.idx]) {
        	//有两点细节 
        	//1.(83~92)行不能改成下面的代码
			//if (a[tem.idx] + p > tema[tem.idx]) continue
			//因为continue之后回溯的步数就会少1(不妨手推一下) 
			//(我考试时就是这里没调出来) 
			//2.tem.idx也不是可以无限加下去的,Ta不能超过原元素的大小 
			//(这个点让我对拍+调试了1h+) 
        	tem = q.top (); q.pop ();
		}
        a[tem.idx] += p; 
        tem.val += p;
        q.push (tem);
        //(93~95)行都是回溯 
    }
    sort (a + 1, a + 1 + n);
    for (int i = 1; i <= n; i++) {
		write (a[i]); putchar (' ');
    }
}

signed main () {
    read (n); read (k); read (p);
    for (int i = 1; i <= n; i++) {
        read (a[i]);
    }
    sort (a + 1, a + 1 + n);
    memcpy (tema, a, sizeof a);
    
    if (p == 0) {//等于0时,除数就为0了,需要特判 
        for (int i = 1; i <= n; i++)
            printf ("%lld ", a[i]);
        return 0;
    }
    
    LL l = 0, r = Maxr * 2 / p;
    //二分,最后的 a[n] 为 a[n] - mid * p 
    while (l + 1 < r) {
        LL mid = l + r >> 1;
        if (check (mid)) r = mid;
        else l = mid;
    }
    if (check (l)) solve (l);
    else solve (r);
    return 0;
}

3.小结

平时太依赖于评测机和数据了,一会改不出来就去下数据,平时的散漫就导致考试时要求有限的时间时,就不能很快的跳出来。

之后我一定要改正,不然就会像今天这样,有思路但是打不出来,细节考虑不到,令人唏嘘,后悔不尽。

posted @ 2021-05-21 22:47  C2022lihan  阅读(45)  评论(0编辑  收藏  举报