loj #535. 「LibreOJ Round #6」花火 树状数组求逆序对+主席树二维数点+整体二分...

$ \color{#0066ff}{ 题目描述 }$

「Hanabi, hanabi……」

一听说祭典上没有烟火,Karen 一脸沮丧。

「有的哦…… 虽然比不上大型烟花就是了。」

还好 Shinobu 早有准备,Alice、Ayaya、Karen、Shinobu、Yoko 五人又能继续愉快地玩耍啦!

「噢……!不是有放上天的烟花嘛!」Karen 兴奋地喊道。

「啊等等……」Yoko 惊呼。Karen 手持点燃引信的烟花,「嗯??」

Yoko 最希望见到的是排列优美的烟火,当然不会放过这个机会…… 不过时间似乎已经不多了。

nn 个烟火排成一排,从左到右高度分别为 \(h_1,h_2,\cdots\),这些高度两两不同。

每次 Yoko 可以选择两个相邻的烟火交换,这样的交换可以进行任意多次。

每次 Yoko 还可以选择两个不相邻的烟火交换,但这样的交换至多进行一次。

你的任务是帮助 Yoko 用最少次数的交换,使这些烟火从左到右的高度递增。

\(\color{#0066ff}{输入格式}\)

第一行包含一个正整数 \(n\)

第二行包含 \(n\) 个正整数 \(h_1,h_2,\cdots\),相邻整数之间用一个空格隔开。

\(\color{#0066ff}{输出格式}\)

输出一个整数,表示最少的交换次数。

\(\color{#0066ff}{输入样例}\)

5
3 5 4 1 2

\(\color{#0066ff}{输出样例}\)

5

\(\color{#0066ff}{数据范围与提示}\)

一开始,\(5\) 个烟火的高度依次为 \(3,5,4,1,2\)

\(1\) 次,交换第 \(4\) 根烟火和第 \(5\) 根烟火,交换后烟火的高度依次为 \(3,5,4,2,1\)

\(2\) 次,交换第 \(3\) 根烟火和第 \(4\) 根烟火,交换后烟火的高度依次为 \(3,5,2,4,1\)

\(3\) 次,交换第 \(1\) 根烟火和第 \(2\) 根烟火,交换后烟火的高度依次为 \(5,3,2,4,1\)

\(4\) 次,交换第 \(2\) 根烟火和第 \(3\) 根烟火,交换后烟火的高度依次为 \(5,2,3,4,1\)

\(5\) 次,交换第 \(1\) 根烟火和第 \(5\) 根烟火,交换后烟火的高度依次为 \(1,2,3,4,5\)

可以证明这是交换次数最少的方案。

5c7e1ba718bc7.png

\(\color{#0066ff}{题解}\)

考虑没有交换任意两个数一次的操作,那么答案就是逆序对数
现在我们有一个交换任意两个数的操作
我们肯定是要让这次操作的贡献最大的
也就是说,减少的逆序对数最多
那么我们选的两个数,左面那个肯定是越靠左且越大为优,右面那个越靠右越小越优
也就是说,我们可能被交换的数就是所有前缀max取得的点和所有后缀min取得的点
然后我们处理出了这两个可能修改的位置数组,都是单调的
我们现在要在两个数组内分别选一个数,交换这两个数所对应的位置,使得对答案的贡献最大
对于一对\([l,r]\),交换它俩的贡献是\([l,r]之间权值在[a[r],a[l]]之间的值的个数\)
这就是二维数点了, 主席树可以维护
现在考虑怎么快速求出每个点的贡献
也就是说,对于每个可能的右端点,选一个最优的左端点

5c7e1f3adfcf5.png

我们假设1选择y最优
那么可以得出\(G+H>F+I\)
然后对于2,因为\(G+H+J>F\)显然,所以y肯定比x有,也就是说最优点单调!!
于是我们就可以整体二分\(O(nlog^2n)\)的求出右面每个点的贡献,然后取max,交换最优的l和r
最后树状数组求一下序列逆序对即可
#include<bits/stdc++.h>
#define LL long long
LL in() {
    char ch; LL x = 0, f = 1;
    while(!isdigit(ch = getchar()))(ch == '-') && (f = -f);
    for(x = ch ^ 48; isdigit(ch = getchar()); x = (x << 1) + (x << 3) + (ch ^ 48));
    return x * f;
}
const int maxn = 3e5 + 10;
struct Tree {
protected:
    int st[maxn];
    int low(int x) { return x & (-x); }
    int siz;
public:
    void resize(int n) { siz = n; }
    void add(int pos) { while(pos <= siz) st[pos]++, pos += low(pos); }
    int query(int pos) { int re = 0; while(pos) re += st[pos], pos -= low(pos); return re; }
}s;
struct node {
    node *ch[2];
    int num;
    node(int num = 0): num(num) { ch[0] = ch[1] = NULL; }
};
int n, a[maxn];
node *root[maxn];
int st1[maxn], st2[maxn], top1, top2, to[maxn], ans[maxn];
LL tot;
void init() {
    root[0] = new node();
    root[0]->ch[0] = root[0]->ch[1] = root[0];
}
void add(node *&o, node *lst, int l, int r, int p) {
    o = new node(), *o = *lst, o->num++;
    if(l == r) return;
    int mid = (l + r) >> 1;
    if(p <= mid) add(o->ch[0], lst->ch[0], l, mid, p);
    else add(o->ch[1], lst->ch[1], mid + 1, r, p);
}
int getnum(node *x, node *y, int l, int r, int ql, int qr) {
    if(x->num == y->num) return 0;
    if(ql <= l && r <= qr) return y->num - x->num;
    int mid = (l + r) >> 1;
    int cnt = 0;
    if(ql <= mid) cnt += getnum(x->ch[0], y->ch[0], l, mid, ql, qr);
    if(qr > mid) cnt += getnum(x->ch[1], y->ch[1], mid + 1, r, ql, qr);
    return cnt;
}
int getans(int l, int r) {
    if(l >= r) return 0;
    if(a[l] < a[r]) return 0;
    return getnum(root[l - 1], root[r], 1, n, a[r] + 1, a[l] - 1);
}
void cdq(int l, int r, int a, int b) {
    if(a > b) return;
    if(l > r) return;
    if(l == r) {
        for(int i = a; i <= b; i++) {
            int now = getans(st1[l], st2[i]);
            if(now > ans[i]) ans[i] = now, to[i] = l;
        }
        return;
    }
    int mid = (a + b) >> 1;
    //printf("now is work[%d, %d, %d, %d]\n", l, r, a, b);
    ans[mid] = 0;
    for(int i = l; i <= r; i++) {
        int now = getans(st1[i], st2[mid]);
        if(now >= ans[mid]) ans[mid] = now, to[mid] = i;
    }
    cdq(l, to[mid], a, mid - 1);
    cdq(to[mid], r, mid + 1, b);
}
void predoit() {
    s.resize(n);
    init();
    for(int i = 1; i <= n; i++) add(root[i], root[i - 1], 1, n, a[i] = in());
    //st1 can be left
    //st2 can be right
    for(int i = 1; i <= n; i++) {
        while(top2 && a[i] < a[st2[top2]]) top2--;
        if(a[i] > a[st1[top1]]) st1[++top1] = i;
        st2[++top2] = i;
    }
    int max = 0, l = 0, r = 0;
    cdq(1, top1, 1, top2);
    for(int i = 1; i <= top2; i++)
        if(max < ans[i]) max = ans[i], l = st1[to[i]], r = st2[i];
    std::swap(a[l], a[r]);
    if(l ^ r) tot++;
}
void getans() {
    for(int i = n; i >= 1; i--) {
        tot += s.query(a[i]);
        s.add(a[i]);
    }
    printf("%lld\n", tot);
}
int main() {
    n = in();
    predoit();
    getans();
    return 0;
}

转载于:https://www.cnblogs.com/olinr/p/10476925.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值