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";
}
posted @ 2022-04-25 16:04  illume  阅读(89)  评论(0编辑  收藏  举报