loj535. 「LibreOJ Round #6」花火

题意

对于一个序列,给你一次任意交换一对数的机会,剩下操作只能交换相邻两个,问序列排成升序的最小次数。

题解

暴力的话就是用原序列逆序对数减去开始交换一对数逆序对减少的最多个数。
考虑如何优化。
考虑我们交换了两个数\(i, j\),一定满足\(h_i > h_j\),否则交换不优。
而且减少的逆序对是满足如下条件\(x\)个数的两倍:

  1. \(i < x < j\)
  2. \(h_i > h_x > h_j\)

那么,把\((i, h_i)\)看成一个点,就是一个与二维数点相关的题,关于数对\((i, j)\)有贡献的\(x\)的个数就是以\((i, h_i)\)为左上角,\((j, h_j)\)为右下角的矩形内的点的个数。
我们猜测有个结论:在最优解中,我们只可能选择左上角(以其为原点的第二象限)没有点的点作为矩形的左上角,令其属于集合\(S\);同样,只可能选择右下角(以其为原点的第四象限)没有点的点作为矩形的右下角,令其属于集合\(T\)
这两类点我们可以预处理出来,并且对于两类点都各满足性质:对于同类(同集合)中的两个点\(i, j\),如果\(i < j\),则\(h_i < h_j\),且具有单调性。
这个性质有什么用?
我们可以考虑对于一个位置\(x\),它对于哪些位置对\(<i, j>\)有贡献(其中\(i \in S, j \in T\))。
由于\(h_i > h_x\)\(i < x\),有\(i \in [l, x - 1]\),其中\(l\)满足最小的满足\(l \in S\)\(h_l > h_x\)的位置;
由于\(h_x > h_j\)\(x < j\),有\(j \in [x + 1, r]\),其中\(r\)满足最大的满足\(r \in T\)\(h_x > h_r\)的位置;
\(l, r\)都可以二分得到。
如果我们把位置对\(<i, j>\)看做一个点(又不同于上面的点),那么对于位置\(x\),得到\(x\)产生贡献的地方是一个以\((l, x + 1)\)为左下角,\((x - 1, r)\)为右上角的矩形(有可能这个矩形不合法,即不存在)。
然后问题转变成求每个点被多少矩形覆盖数的\(\max\),用线段树+扫描线即可。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 3e5 + 5;
int n, tot; ll ans;
int a[N], b[N], c[N];
int pre[N], ipre[N], suc[N], isuc[N];
int mx[N << 2], tg[N << 2];
struct seg {
    int h, l, r, w;
    bool operator < (const seg &o) const {
        return h < o.h;
    }
} s[N << 1];
void msort (int l, int r) {
    if (l == r) {
        return;
    }
    int mid = (l + r) >> 1, u = l, v = mid + 1;
    msort(l, mid), msort(mid + 1, r);
    for (int i = l; i <= r; ++i) {
        if (v > r || (u <= mid && b[u] < b[v])) {
            c[i] = b[u++];
        } else {
            c[i] = b[v++];
            ans += mid - u + 1;
        }
    }
    for (int i = l; i <= r; ++i) {
        b[i] = c[i];
    }
}
void prework () {
    b[0] = 0;
    for (int i = 1; i <= n; ++i) {
        b[i] = max(b[i - 1], a[i]);
        if (b[i] == a[i]) {
            pre[++pre[0]] = a[i];
            ipre[pre[0]] = i;
        }
    }
    c[n + 1] = 1e9;
    for (int i = n; i; --i) {
        c[i] = min(c[i + 1], a[i]);
        if (c[i] == a[i]) {
            suc[++suc[0]] = a[i];
            isuc[suc[0]] = i;
        }
    }
    reverse(suc + 1, suc + suc[0] + 1);
    reverse(isuc + 1, isuc + suc[0] + 1);
    suc[suc[0] + 1] = 1e9;
    for (int i = 1, l, r, L, R; i <= n; ++i) {
        l = ipre[lower_bound(pre + 1, pre + pre[0] + 1, a[i]) - pre];
        L = i - 1;
        r = isuc[upper_bound(suc + 1, suc + suc[0] + 2, a[i]) - suc - 1];
        R = i + 1;
        if (l <= L && R <= r) {
            s[++tot] = (seg) {R, l, L, 1};
            s[++tot] = (seg) {r + 1, l, L, -1};
        }
    }
}
void pushup (int o) {
    mx[o] = max(mx[o << 1], mx[o << 1 | 1]);
}
void pushdown (int o) {
    if (tg[o]) {
        mx[o << 1] += tg[o], tg[o << 1] += tg[o];
        mx[o << 1 | 1] += tg[o], tg[o << 1 | 1] += tg[o];
        tg[o] = 0;
    }
}
void modify (int o, int l, int r, int x, int y, int z) {
    if (x <= l && r <= y) {
        mx[o] += z, tg[o] += z;
        return;
    }
    pushdown(o);
    int mid = (l + r) >> 1;
    if (x <= mid) {
        modify(o << 1, l, mid, x, y, z);
    }
    if (y > mid) {
        modify(o << 1 | 1, mid + 1, r, x, y, z);
    }
    pushup(o);
}
int query (int o, int l, int r, int x, int y) {
    if (x <= l && r <= y) {
        return mx[o];
    }
    pushdown(o);
    int mid = (l + r) >> 1, ret = 0;
    if (x <= mid) {
        ret = max(ret, query(o << 1, l, mid, x, y));
    }
    if (y > mid) {
        ret = max(ret, query(o << 1 | 1, mid + 1, r, x, y));
    }
    return ret;
}
int solve (int ret = 0) {
    s[0] = (seg) {-1, -1, -1, -1};
    sort(s + 1, s + tot + 1);
    for (int i = 1; i <= tot; ++i) {
        if (i > 1 && s[i].h != s[i - 1].h) {
            ret = max(ret, mx[1]);
        }
        modify(1, 1, n, s[i].l, s[i].r, s[i].w);
    }
    return ret;
}
int main () {
    scanf("%d", &n);
    for (int i = 1; i <= n; ++i) {
        scanf("%d", &a[i]);
        b[i] = a[i];
    }
    msort(1, n);
    prework();
    printf("%lld\n", ans - solve() * 2);
    return 0;
}

转载于:https://www.cnblogs.com/psimonw/p/11228048.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值