2024國慶做題總結

bryce_yyds發表於2024-09-28

Secret Santa

思路

這是一個需要深思熟慮的貪心,總之還算有點複雜。

首先,如果一個數不在它自己數值的下標上,就可以填進去,將剩下的還未填的數記錄下來,此時情況如下(樣例1,第一組):

當前:2 1 _
剩餘:3

然後將剩餘的數的那個陣列反過來,即從大到小排序,填滿空位,這樣可能會有衝突,但是,可以證明只會有一個衝突。

證明:\(x,y,z\) 為下標,\(a,b,c\) 分別為 \(x,y,z\) 上的數值,假設 \(y = b\),因為 \(x < y < z\)\(a > b > c\),所以其餘任何位置都無衝突。

那麼如何解決掉衝突呢,將衝突位置上的值替換為之前這個位置上的值即可,再將之前這個位置上的值的現在的位置上的值改為衝突位置的值,這樣就不會有衝突。

原來:2 1 3 位置 3 上有衝突
更改後:3 1 2 (第三個位置改為原來的值,第一個位置改為 3)

顯然這種方案變換次數最少。

程式碼

#include<iostream>
#include<vector>
using namespace std;

inline int read(){register int x = 0, f = 1;register char c = getchar();while (c < '0' || c > '9'){if (c == '-') f = -1;c = getchar();}while (c >= '0' && c <= '9'){x = (x << 1) + (x << 3) + (c ^ 48);c = getchar();}return x * f;}
inline void write(int x){if (x < 0) putchar('-'), x = -x;if (x > 9) write(x / 10);putchar(x % 10 + '0');}

const int N = 2e5 + 10;
int T, n, ans;
int a[N], b[N], cnt[N], tot;
vector<int> v[N];
bool vis[N], f[N];

int main(){
    T = read();
    while (T--){
        n = read();
        for (int i = 1; i <= n; i++) a[i] = read(), v[a[i]].push_back(i);
        for (int i = 1; i <= n; i++){
            for (auto j : v[i]){
                if (!vis[j] && i != j){
                    b[j] = i;
                    vis[j] = 1;
                    f[i] = 1;
                    break;
                }
            }
            if (!f[i]) cnt[++tot] = i;
        }
        for (int i = 1, l = tot; i <= n && l >= 1; i++){
            if (!vis[i]) b[i] = cnt[l], l--;
        }
        for (int i = 1; i <= n; i++){
            if (b[i] == i) swap(b[i], b[v[a[i]][0]]);
        }
        for (int i = 1; i <= n; i++){
            if (a[i] == b[i]) ans++;
        }
        cout << ans << '\n';
        for (int i = 1; i <= n; i++) cout << b[i] << ' ';
        cout << '\n';
        for (int i = 1; i <= n; i++) a[i] = b[i] = cnt[i] = vis[i] = f[i] = 0;
        for (int i = 1; i <= n; i++) v[i].clear();
        tot = ans = 0;
    }
    return 0;
}