Codeforces Round 1024 (Div. 2) D题题解记录

大致题意:给定一个排列aaa,可以进行不限次数操作:swap(ai,ai+2)swap(a_i,a_{i+2})swap(ai,ai+2)同时swap(ai+1,ai+3)swap(a_{i+1},a_{i+3})swap(ai+1,ai+3)。问在经过任意次操作以后,字典序最小的aaa

解:
首先,手玩一下发现,奇数位置的数永远会在奇数位置,偶数位置同理。由此我们开始考虑分开处理奇偶位置的数。
此时产生第一个错解:分别sortsortsort奇偶位置的数然后合并,不出意外的过了样例然后Wrong answer on test 2
然后再造一些样例,发现奇偶位置的数并不是可以任意排布的。如图:
在这里插入图片描述
发现可以在保证奇数位置数保持不变的情况下,使得偶数位置的数发生循环换位,并非任意排布。由此产生第二个错解:用最小表示法分别处理奇偶位置的数。当然也是wa了。不过也学习了最小表示法。
然后我们继续手玩样例:分奇偶这个思路没问题,但是又发现一个样例:请添加图片描述
在此情况下,1要往前跳三个奇数位,同时为了保持偶数位置相对次序不变,最后的2和3的次序发生了改变。原本奇数位在循环移位后应该是1 4 3 2,但是现在变成了1 4 2 3 。由此我们开始考虑计算逆序对:假如单独考虑奇数位,总共有偶数个逆序对,那么最后一定能变成完美的sortsortsort过后的次序;如果有奇数个逆序对,那么最后把最大和次大两个数的位置交换一下。偶数位置处理同理。
用线段树分别计算一下两个位置的逆序对的数量,然后考虑是否交换最后两个位置的数。然后发现样例都过不了。
对于3 4 1 2 这个样例,最小的字典序肯定是 1 2 3 4。但是按照思路,奇数位有1个逆序对,所以是3 1,偶数位也是,为4 2 。思路还是有点问题。再考虑对于操作过后的序列的最后四位ABCD。如果AC所在的位置(奇数位置或者偶数位置)逆序对数量为奇数个导致了A>C,那么考虑字典序的话,我们一定可以交换最后四个数使其变成CDAB。且CDAB一定是小于ABCD的。
由此得到正解:
首先分别处理奇数位置和偶数位置的逆序对的个数,如果有偶数个逆序对,那么最后形成的一定是sortsortsort过后的次序;如果是奇数个,那么最后面两个数要交换一下。合并以后,对于最后四个数,如果倒数第四个数大于倒数第二个数,那么我们不用考虑倒数第一个和倒数第三个数的大小关系,进行一次操作使得倒数第四个数小于倒数第二个数。

auto ans(vector<int> &a) {
    vector<int> temp = a;

    sort(temp.begin(), temp.end());
    temp.erase(unique(temp.begin(), temp.end()), temp.end());
    for (int i = 0; i < a.size(); i++) {
        a[i] = lower_bound(temp.begin(), temp.end(), a[i]) - temp.begin();
    }
    vector<int> t(a.size() * 4);
    int recnt = 0;
    int mx = a.size() - 1;
    function<void(int, int, int, int)> in = [&](int x, int l, int r, int val) {
        if (l == r) {
            t[x]++;
            return;
        }
        int mid = (l + r) >> 1;
        if (val <= mid) in(x << 1, l, mid, val);
        else in(x << 1 | 1, mid + 1, r, val);
        t[x] = t[x << 1] + t[x << 1 | 1];
    };
    function<int(int, int, int, int, int)> que = [&](int x, int l, int r, int ll, int rr) {
        if (ll <= l && rr >= r) return t[x];
        int mid = (l + r) >> 1;
        if (ll > r || rr < l) return 0ll;
        return que(x << 1, l, mid, ll, rr) + que(x << 1 | 1, mid + 1, r, ll, rr);
    };

    for (int i = a.size() - 1; i >= 0; i--) {
        recnt += que(1, 0, mx, 0, a[i] - 1);
        in(1, 0, mx, a[i]);
    }
    if (recnt % 2) {
        swap(temp[mx], temp[mx - 1]);
        return temp;
    } else {
        return temp;
    }

}


void solve() {
    int n;
    cin >> n;
    vector<vector<int>> a(2);
    for (int i = 1; i <= n; i++) {
        int x;
        cin >> x;
        a[i % 2].push_back(x);
    }
    auto odd = ans(a[1]);
    auto even = ans(a[0]);
    int oi = 0, ei = 0;
    if (n % 2) {
        if (even[even.size() - 2] > even[even.size() - 1]) {
            swap(even[even.size() - 2], even[even.size() - 1]);
            swap(odd[odd.size() - 2], odd[odd.size() - 1]);
        }
    } else {
        if (odd[odd.size() - 2] > odd[odd.size() - 1]) {
            swap(odd[odd.size() - 2], odd[odd.size() - 1]);
            swap(even[even.size() - 2], even[even.size() - 1]);
        }
    }
    for (int i = 1; i <= n; i++) {
        if (i % 2) cout << odd[oi++] << " ";
        else cout << even[ei++] << " ";
    }
    cout << endl;

}
	

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值