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;
}