天天看點

集合操作題解+小結

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.小結

平時太依賴于評測機和資料了,一會改不出來就去下資料,平時的散漫就導緻考試時要求有限的時間時,就不能很快的跳出來。

之後我一定要改正,不然就會像今天這樣,有思路但是打不出來,細節考慮不到,令人唏噓,後悔不盡。