UVA 11990 `Dynamic'' Inversion CDQ分治, 归并排序, 树状数组, 尺取法, 三偏序统计 难度: 2

题目

https://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&problem=3141

题意

一个1到n的排列,每次随机删除一个,问删除前的逆序数

思路

综合考虑,对每个数点,令value为值,pos为位置,time为出现时间(总时间-消失时间),明显是统计value1 > value2, pos1 < pos2, time1 < time2的个数

首先对其中一个轴排序,比如value,这样在归并过程中,左子树的value总是小于右子树的,可以分治。

当左右子树包含哪些数点已经确定后,可以用自下而上的归并排序使得子树上的数点按照第二维相对有序,方便用尺取法统计子树之间的逆序数。

第三维通过树状数组进行压缩,加快统计速度。

注意仅仅统计左子树对右子树的影响,就会错过右子树中的数点出现的比较晚的情况。因此需要统计右子树对左子树的影响,此时注意别把同一时间出现的重复计数。

感想

1. 注意long long!!!

2. BIT的上限要>=n!

3. 注意统计影响完成后需要清空树状数组(区间大小已经减少了所以可以浪费地使用),此时不能直接用memset清空整个数组,时间会成为O(n2),超时。

代码

时间: 0.250s

时间复杂度O(cnlogn)

#include <algorithm>
#include <cassert>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <queue>
#include <tuple>
#include <cassert>

using namespace std;

const int MAXN = int(4e5 + 4);

#define LEFT_CHILD(x) ((x) << 1)
#define RIGHT_CHILD(x) (((x) << 1) + 1)
#define FATHER(x) ((x) >> 1)
#define IS_LEFT_CHILD(x) (((x) & 1) == 0)
#define IS_RIGHT_CHILD(x) (((x) & 1) == 1)
#define BROTHER(x) ((x) ^ 1)
#define LOWBIT(x) ((x) & (-x))

#define LOCAL_DEBUG 

struct Node{
    int value, pos, time;
}nodes[MAXN], tmpNodes[MAXN];

int timeCnt[MAXN * 4];
long long revNum[MAXN];
int clearStack[MAXN];
int clearLen;
int n, m;
int bitLimit;

int getHigherBit(int n) {
    int x = 1;
    while (x < n) { x <<= 1; }
    return x;
}

void update(int id) {
    while (id <= bitLimit) {
        if (timeCnt[id] == 0) {
            clearStack[clearLen++] = id;
        }
        timeCnt[id]++;
        id += LOWBIT(id);
    }
}

void clearCnt() {
    while (clearLen > 0) {
        timeCnt[clearStack[--clearLen]] = 0;
    }
}

int countTimesSmaller(int id) {
    if (id < 0)return 0;
    int sum = 0;
    int tmp = 0;
    while (id > 0) {
        sum += timeCnt[id];
        id -= LOWBIT(id);
    }
    return sum;
}

void merge_by_pos(int root_ind, int internal_l, int internal_r) {
    int internal_mid = (internal_l + internal_r) >> 1;
    for (int i = internal_l; i <= internal_r; i++) {
        tmpNodes[i] = nodes[i];
    }
    for (int i = internal_l, j = internal_mid + 1, ind = internal_l; ind <= internal_r; ) {
        if (i > internal_mid) {
            nodes[ind++] = tmpNodes[j++];
        }
        else if (j > internal_r) {
            nodes[ind++] = tmpNodes[i++];
        }
        else if (tmpNodes[i].pos < tmpNodes[j].pos) {
            nodes[ind++] = tmpNodes[i++];
        }
        else {
            nodes[ind++] = tmpNodes[j++];
        }
    }
}
void cal(int root_ind, int internal_l, int internal_r) {
    if (internal_l == internal_r)return;
    int internal_mid = (internal_l + internal_r) >> 1;
    if(internal_l != internal_mid)cal(LEFT_CHILD(root_ind), internal_l, internal_mid);
    if (internal_mid + 1 != internal_r)cal(RIGHT_CHILD(root_ind), internal_mid + 1, internal_r);
//    printf("L Node: %d[%d, %d] LC: %d[%d, %d], RC: %d[%d, %d]\n", root_ind, internal_l, internal_r, LEFT_CHILD(root_ind), internal_l, internal_mid, RIGHT_CHILD(root_ind), internal_mid + 1, internal_r);
    for (int i = internal_l, j = internal_mid + 1; i <= internal_mid; i++) {
        while (j <= internal_r && nodes[i].pos > nodes[j].pos) {
            update(nodes[j].time);
            j++;
        }
        revNum[nodes[i].time] += countTimesSmaller(nodes[i].time);
//        printf("L (%d, %d, %d): +%d\n", nodes[i].value, nodes[i].pos, nodes[i].time, countTimesSmaller(nodes[i].time));
    }
    clearCnt();

    for (int i = internal_mid, j = internal_r; j > internal_mid; j--) {
        while (i >= internal_l && nodes[i].pos > nodes[j].pos) {
            update(nodes[i].time);
            i--;
        }
        revNum[nodes[j].time] += countTimesSmaller(nodes[j].time - 1);
//        printf("R (%d, %d, %d): +%d\n", nodes[j].value, nodes[j].pos, nodes[j].time, countTimesSmaller(nodes[j].time - 1));
    }
    clearCnt();
    merge_by_pos(root_ind, internal_l, internal_r);

}

int main() {
#ifdef LOCAL_DEBUG
    freopen("input.txt", "r", stdin);
    freopen("output2.txt", "w", stdout);
#endif // LOCAL_DEBUG
    for (int ti = 1; scanf("%d%d", &n, &m) == 2; ti++) {
        bitLimit = getHigherBit(n);
        for (int i = 1; i <= n; i++) {
            int tmp;
            scanf("%d", &tmp);
            nodes[tmp].value = tmp;
            nodes[tmp].pos = i;
            nodes[tmp].time = 1;
        }
        for (int i = 1; i <= m + 1; i++) { revNum[i] = 0; }
        for (int i = 0; i < m; i++) {
            int tmp;
            scanf("%d", &tmp);
            nodes[tmp].time = m - i + 1;
        }
        cal(1, 1, n);
        long long ans = 0;
        for (int i = 1; i <= m + 1; i++) { ans += revNum[i]; }
        for (int i = 0; i < m; i++) {
            printf("%lld\n", ans);
            ans -= revNum[m - i + 1];
        }
    }
    return 0;
}

UVA 11990 `Dynamic'' Inversion CDQ分治, 归并排序, 树状数组, 尺取法, 三偏序统计 难度: 2

原文地址:https://www.cnblogs.com/xuesu/p/10356068.html

时间: 2024-10-12 22:59:30

UVA 11990 `Dynamic'' Inversion CDQ分治, 归并排序, 树状数组, 尺取法, 三偏序统计 难度: 2的相关文章

[CDQ分治][Treap][树状数组]Theresa与数据结构

Description 这是个复杂的世界.人类社会,自然界,还有地球之外的银河--每一天日出日落,人来人往,步履匆匆.究竟是为什么呢?那支配着一切的至高无上的法则又是否存在呢?Theresa知道,这个问题并不是一朝一夕就可以解答的,只有在仔细.深入的观察和思考以后,才有可能将所有支离破碎的线索联系起来,从而隐约窥见真实的答案.于是,Theresa经常思考生活中遇到的大大小小的问题.为什么港台出版的书籍里印刷的汉字她一个也不认识呢?为什么隔夜的白开水中富含一氧化二氢呢?为什么每年都有一段时间Gma

UVA 11990 ``Dynamic&#39;&#39; Inversion

26天以前做过的一道题,之前的做法是分治预处理,树套树在线修改,复杂度为O(nlogn+m*logn*logn),代码量较大. 本来想学习一下cdq分治的,看到论文上的凸包.斜率就暂时放一边了,只知道和一般的分治的不同是左子问题可以用来解决右边的子问题. 今天下午YY了一个离线的分治做法. 对于每一个数字,构成逆序对除了val大小还和被删除的时间del有关,这实际上是一个三维偏序的问题. 一个元素是一个三元组e(pos,val,del),e1和e2对答案有贡献当且仅当e1.pos < e1.po

UVA 11990 ”Dynamic“ Inversion(线段树+树状数组)

[题目链接] UVA11990 [题目大意] 给出一个数列,每次删去一个数,求一个数删去之前整个数列的逆序对数. [题解] 一开始可以用树状数组统计出现的逆序对数量 对于每个删去的数,我们可以用线段树求出它在原序列中的逆序对贡献 在线段树的每个区间有序化数据,就可以二分查找出这个数在每个区间的位置, 这样就处理出了划分出的区间的贡献,先用答案减去这一部分 接下来考虑已经删去部分的容斥,我们发现只要对删去部分再做一次类似的操作, 将这一部分跟当前删去数求一次贡献就是刚才多减去的部分,将这部分的答案

代码与算法集锦-归并排序+树状数组+快排+深度优先搜索+01背包(动态规划)

归并排序 求逆序数 归并排序是建立在归并操作上的一种有效的排序算法.该算法是采用分治法(Divide and Conquer)的一个非常典型的应用. 首先考虑下如何将将二个有序数列合并.这个非常简单,只要从比较二个数列的第一个数,谁小就先取谁,取了后就在对应数列中删除这个数.然后再进行比较,如果有数列为空,那直接将另一个数列的数据依次取出即可. //将有序数组a[]和b[]合并到c[]中 void MemeryArray(int a[], int n, int b[], int m, int c

nyist oj 117 求逆序数 (归并排序&amp;&amp;树状数组)

求逆序数 时间限制:2000 ms  |  内存限制:65535 KB 难度:5 描述 在一个排列中,如果一对数的前后位置与大小顺序相反,即前面的数大于后面的数,那么它们就称为一个逆序.一个排列中逆序的总数就称为这个排列的逆序数. 现在,给你一个N个元素的序列,请你判断出它的逆序数是多少. 比如 1 3 2 的逆序数就是1. 输入 第一行输入一个整数T表示测试数据的组数(1<=T<=5) 每组测试数据的每一行是一个整数N表示数列中共有N个元素(2〈=N〈=1000000) 随后的一行共有N个整

Ultra-QuickSort (poj 2299 归并排序 || 树状数组 求逆序对)

Language: Default Ultra-QuickSort Time Limit: 7000MS   Memory Limit: 65536K Total Submissions: 45751   Accepted: 16615 Description In this problem, you have to analyze a particular sorting algorithm. The algorithm processes a sequence of n distinct i

1090. In the Army Now (Ural 1090 归并排序||树状数组)

1090. In the Army Now Time limit: 1.0 second Memory limit: 64 MB The sergeant ordered that all the recruits stand in rows. The recruits have formed K rows with Npeople in each, but failed to stand according to their height. The right way to stand in

【XSY2669】归并排序 树状数组 简单组合数学

题目描述 有一个长度为\(n\)的排列\(n=2^k\),你要把这个数组归并排序.但是在长度为\(2\)的时候有\(\frac{1}{2}\)的概率会把两个数交换(就是有\(\frac{1}{2}\)的概率返回错的结果).有两种操作 \(1\):交换两个数 \(2\):询问排序后的一个位置等于一个数的概率. \(k\leq 16,q\leq {10}^5\) 题解 这个排序有点奇怪.两个数\(a,b(a<b)\)排序后可能是\(ab\)也可能是\(ba\). 观察到\(ab\)会正常排序,而\(

CF833D Red-Black Cobweb 点分治、树状数组

传送门 统计所有路径的边权乘积的乘积,不难想到点分治求解. 边权颜色比例在\([\frac{1}{2},2]\)之间,等价于\(2B \geq R , 2R \geq B\)(\(R,B\)表示红色和黑色的边的条数) 所以我们可以在统计的时候,先把所有可能的路径全部乘进答案,然后除掉满足\(2B < R\)或者\(2R < B\)的路径的乘积.显然对于一条路径,这两个条件至多满足一个. 对于两条路径,它们红色.黑色的边数分别为\(B_1,R_1\)和\(B_2,R_2\),那么需要统计的就是\