校内训练0531 逆序对

【题目大意】

有一棵2n-1个节点的二叉树,它有恰好n个叶子节点,每个叶子节点上写了一个整数。如果将这棵树的所有叶子节点上的数从左到右写下来,便得到一个序列a[1]…a[n]。现在想让这个序列中的逆序对数量最少,但唯一的操作就是选树上一个非叶子节点,将它的左右两颗子树交换。你可以做任意多次这个操作。求在最优方案下,该序列的逆序对数最少有多少。

n<=200000

【题解】
啊很明显我们对于每个节点 判断一下两边交换/不交换哪个逆序对贡献的少就行了

至于这个逆序对贡献啊?线段树合并!

啊我不会线段树合并啊?启发式合并(dsu on tree)

# include <vector>
# include <stdio.h>
# include <assert.h>
# include <string.h>
# include <iostream>
# include <algorithm>
// # include <bits/stdc++.h>

using namespace std;

typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
const int M = 5e5 + 10;
const int mod = 1e9+7;

# define RG register
# define ST static

int n, siz, rt, ch[M][2], siz2;
int val[M], a[M], m, sz[M], sz2[M];
vector<int> ps;

struct BIT {
    int c[M], n;
    # define lb(x) (x&(-x))
    inline void set(int _n) {
        n = _n;
        memset(c, 0, sizeof c);
    }
    inline void edt(int x, int d) {
        for (; x<=n; x+=lb(x)) c[x] += d;
    }
    inline int sum(int x) {
        int ret=0;
        for (; x; x-=lb(x)) ret += c[x];
        return ret;
    }
    inline int sum(int l, int r) {
        if(l>r) return 0;
        return sum(r)-sum(l-1);
    }
}T;

inline void dfs(int &x) {
    int t; scanf("%d", &t);
    if(t == 0) x = ++siz;
    else x = ++siz2;
    sz[x] = 1;
    if(t == 0) {
        dfs(ch[x][0]);
        dfs(ch[x][1]);
        sz[x] += sz[ch[x][0]] + sz[ch[x][1]];
        sz2[x] = sz2[ch[x][0]] + sz2[ch[x][1]];
    } else {
        sz2[x] = 1;
        val[x] = t;
        ps.push_back(t);
    }
}

bool big[M];
ll cura = 0, curb;
inline void calc(int x) {
    if(val[x] != 0) {
        cura += T.sum(val[x]+1, n);
        curb += T.sum(val[x]-1);
        return ;
    }
    if(!big[ch[x][0]]) calc(ch[x][0]);
    if(!big[ch[x][1]]) calc(ch[x][1]);
}

inline void del(int x, int d) {
    if(x <= n) {
        T.edt(val[x], d);
        return ;
    }
    if(!big[ch[x][0]]) del(ch[x][0], d);
    if(!big[ch[x][1]]) del(ch[x][1], d);
}

ll ans = 0;
inline void dsu(int x, bool kep) {
    if(x <= n) {
        if(kep) T.edt(val[x], 1);
        return;
    }
    int bc, sc;
    if(sz[ch[x][0]] > sz[ch[x][1]]) bc = ch[x][0], sc = ch[x][1];
    else bc = ch[x][1], sc = ch[x][0];
    dsu(sc, 0);
    dsu(bc, 1);
//    printf("x=%d, query=%d\n", x, T.sum(1, n));
    big[bc] = 1;
    cura = curb = 0;
    calc(x); del(x,1);
    ans += min(cura, curb);
    big[bc] = 0;
    if(kep == 0) del(x,-1);
}

int main() {
    cin >> n;
    siz = n; dfs(rt);
    T.set(n);
    sort(ps.begin(), ps.end());
    ps.erase(unique(ps.begin(), ps.end()), ps.end());
    for (int i=1; i<=siz; ++i)
        if(val[i] != 0) val[i] = lower_bound(ps.begin(), ps.end(), val[i]) - ps.begin() + 1;
//    for (int i=1; i<=siz; ++i) printf("x=%d, ls=%d, rs=%d\n", i, ch[i][0], ch[i][1]);
    dsu(rt, 1);
    cout << ans << endl;
    return 0;
}

时间: 2024-08-25 12:14:11

校内训练0531 逆序对的相关文章

校内集训 miku set模拟冒泡排序 逆序对

题意 给你一个长为\(n\)的序列,进行\(m\)次操作,每次对一个区间进行排序,求最后的序列\((n <= 1500, m <= 1e6)\) 这道题目思维难度挺大的 对于一个序列,排序的本质就是消除里面的所有逆序对 考虑冒泡排序的过程,每次也是交换\(a[i]>a[i+1]\)这个逆序对 这样子交换最多有\(n^2\)次 那么我们可以用一个数据结构模拟冒泡排序交换逆序对这个过程 用一个\(set\)维护所有逆序对的位置 每次暴力删除区间内所有逆序对 再把新产生的逆序对加入\(set\

BZOJ 3295: [Cqoi2011]动态逆序对 cdq分治

https://www.lydsy.com/JudgeOnline/problem.php?id=3295 这个妹妹我曾见过的~~~ 之前应该在校内oj写了,似乎还写过题解?发现没写博客就重新水一遍代码水一篇博客好了. 把找逆序对的过程想成一个一个往里塞数字然后找每个数字可以组成的逆序对,把最后要去掉的几个也想成往里塞,所以加一个时间维度用cdq求三维偏序. 1 #include<iostream> 2 #include<cstdio> 3 #include<algorith

codeforces 414C C. Mashmokh and Reverse Operation(归并排序求逆序对)

题目链接: C. Mashmokh and Reverse Operation time limit per test 4 seconds memory limit per test 512 megabytes input standard input output standard output Mashmokh's boss, Bimokh, didn't like Mashmokh. So he fired him. Mashmokh decided to go to university

求逆序对(线段树版)

一个序列a1,a2,a3...aN,求出满足:ai > aj 且 i < j 的个数. 一个最容易想到的方法就是枚举所有的i,j看看是否满足,显然是O(n^2)的复杂度.不够好. 可以这样考虑,开一个数组保存这n个数出现的位置和对应的次数,这个数组要开到a数组里最大的那个数MAX,也就是hash,初始状态数组里没有元素,每个数对应的个数都是0. 如果考虑第i个数,找到比它大的所有的数 的个数,查找的范围即 ai+1~MAX,这就是到i这个位置的逆序对的总和,接着把a[i]这个数添加到数组里,也

剑指offer: 数组中的逆序对

1. 最简单的思路,对每个值,遍历与其逆序的数组对:但时间复杂度太高: 2. 归并排序的思路: 先将数组分隔成子数组,先统计出子数组内的逆序对的数目,然后统计两个相邻子数组之间的逆序对的数目: int InversePairsCore(int *data, int * copy, int start, int end) { //递归介绍条件,只剩一个元素 if(start==end) { copy[start]=data[start]; return 0; } int length=(end-s

[HAOI2009]逆序对数列

题目描述 对于一个数列{ai},如果有i<j且ai>aj,那么我们称ai与aj为一对逆序对数.若对于任意一个由1~n自然数组成的数列,可以很容易求出有多少个逆序对数.那么逆序对数为k的这样自然数数列到底有多少个? 输入输出格式 输入格式: 第一行为两个整数n,k. 输出格式: 写入一个整数,表示符合条件的数列个数,由于这个数可能很大,你只需输出该数对10000求余数后的结果. 输入输出样例 输入样例#1: 4 1 输出样例#1: 3 说明 样例说明: 下列3个数列逆序对数都为1:分别是1 2

bzoj3295动态逆序对

Cqoi2011]动态逆序对 Time Limit: 10 Sec  Memory Limit: 128 MBSubmit: 5362  Solved: 1814[Submit][Status][Discuss] Description 对于序列A,它的逆序对数定义为满足i<j,且Ai>Aj的数对(i,j)的个数.给1到n的一个排列,按照某种顺序依次删除m个元素,你的任务是在每次删除一个元素之前统计整个序列的逆序对数. Input 输入第一行包含两个整数n和m,即初始元素的个数和删除的元素个数

【bzoj3295】动态逆序对

我怎么控制不住自己又写了个数据结构啊--真是的-- 其实我是想练CDQ分治的--结果忍不住又写了个主席树. 首先看看不动态的逆序对咋做?树状数组嘛. 那么删除咋搞?就是考虑贡献,把它前面比他大的,后面比他小的减去-- 诶?带修改主席树?我--我好像才写过--? 1 #include<bits/stdc++.h> 2 #define inf 0x7fffffff 3 #define N 100005 4 #define M 5000005 5 using namespace std; 6 typ

[BZOJ3295][Cqoi2011]动态逆序对

试题描述 对于序列A,它的逆序对数定义为满足i<j,且Ai>Aj的数对(i,j)的个数.给1到n的一个排列,按照某种顺序依次删除m个元素,你的任务是在每次删除一个元素之前统计整个序列的逆序对数. 输入 输入第一行包含两个整数n和m,即初始元素的个数和删除的元素个数.以下n行每行包含一个1到n之间的正整数,即初始排列.以下m行每行一个正整数,依次为每次删除的元素. 输出 输出包含m行,依次为删除每个元素之前,逆序对的个数. 输入示例 5 4 1 5 3 4 2 5 1 4 2 输出示例 5 2