Codeforces Global Round 20/F1题解
题意
给定\(n\)个数组成的原始序列\(A\),你需要构造这\(n\)个数的一个排列\(B\),使得最少操作次数交换两个位置的数还原回一开始给定的序列\(A\)的操作次数最多。
思路
首先,我们需要知道一个知识点:
如果已经给定\(B\),\(A\),我们最少几次就可以将\(B\)还原成\(A\)。
结论:
如果我们想要 \(B[i]\) 到 \(A[j]\) 上,我们就建立一条件 \(i \rightarrow j\) 。
最少操作次数等于 \(n\) 减去环的个数(环中不能有相同的元素,自环也算环)
根据这个性质,我们可以直接得到这题我们的贪心策略,我们需要构造一个环的数量最少的序列\(B\)。
于是,我们得到一个很显然的结论,环的数量最少取决于出现次数最多的那个数的次数,我们每次构造最长的环。
但是,这里就会有新的问题,一组好样例:
7
7 2 5 4 7 4 2
如果根据贪心,我们会分为2 -> 7 -> 4 -> 5和7 -> 2 -> 4,那么我们跑出来的序列\(B\)就是2 5 4 7 4 2 7 ,但是此时,序列B的第一个2和最后一个7构成了一个我们并不想出现的环,这样导致环的数量变多,答案也就变小了。问题的原因就在于 存在了2 -> 7的单向边也存在了 7 ->2 的单向边,所以就构成了新环。
所以我们还需要保证相同的两个数字之间的单向边的方向一致,那么我们只需要规定只会从数值小的数字向数值大的数字建边即可。
code
int n;
int a[N];
map<int,vector<int>>pos;
int ans[N];
int tot=0;
PII servive[N];
void solve() {
pos.clear();
tot = 0;
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
if (!pos.count(a[i])) {
servive[++tot] = {0, a[i]};
}
pos[a[i]].pb(i);
}
for (int i = 1; i <= tot; i++) {
servive[i].first = pos[servive[i].second].size();
}
sort(servive + 1, servive + 1 + tot);
reverse(servive + 1, servive + 1 + tot);
while (tot != 0) {
// cout<<tot<<"\n";
vector<PII > tmp;
for (int i = 1; i <= tot; i++) {
int x = servive[i].second;
servive[i].first--;
tmp.pb({x, pos[x].back()});
pos[x].pop_back();
}
sort(tmp.begin(), tmp.end());
for(int i=0;i<tmp.size();i++){
if(i!=tmp.size()-1)ans[tmp[i+1].second]=tmp[i].first;
else ans[tmp[0].second]=tmp[i].first;
}
int q = tot;
for (int i = 1; i <= tot; i++) {
if (servive[i].first == 0) {
q--;
}
}
tot = q;
}
for (int i = 1; i <= n; i++) {
cout << ans[i] << " ";
}
cout << "\n";
}