• 「Luogu P3157」「CQOI2011」「UVA 11990」动态逆序对 / ``Dynamic'' Inversion


    Description

    对于序列 (a),它的逆序对数定义为集合

    ({(i,j)| i<j wedge a_i > a_j })

    中的元素个数。

    现在给出 (1sim n) 的一个排列,按照某种顺序依次删除 (m) 个元素,你的任务是在每次删除一个元素之前统计整个序列的逆序对数。

    Hint

    Luogu 数据:

    • (1le nle 10^5)
    • (1le mle 5 imes 10^4)

    UVA 多组数据且数据规模为 Luogu 的两倍。

    注:保证前 3 个 solution 可以在 UVA 上通过(Solution 1/2 亲测,Solution 3 代码可以参考:https://www.luogu.com.cn/blog/van/solution-uva11990 ),所有的 solution 实现的好都可以在 Luogu 上通过。

    Solution 1

    CDQ分治。

    先计算出初始整个数列的逆序对数,然后逐个减去被删除数对逆序对数的影响。cdq 就是计算这个影响的过程。

    当第 (i) 个数被删去之后,在它前面的,比它大的,且删除时间晚于自己的 数的个数,加上 在它后面的,比它小的,且删除时间晚于自己的 数的个数,就是这个数对总逆序对数的影响。

    假如把这个东西拆成两部分来做,那每一个部分的计算实质上就是变形的 三维数点。合起来一起算也是可以的。

    • 第一维:位置,已经排好序。
    • 第二维:数值大小,cdq 的过程中内部归并排序。
    • 第三维:删除时间,使用树状数组等 DS 维护。

    这个算法看起来海星,可惜不能在线。

    • 时间复杂度:(O(nlog^2 n))
    • 空间复杂度:(O(n))
    • 是否在线:否

    Code for Solution 1

    UVA 代码,Luogu同样可过。

    /*
     * Author : _Wallace_
     * Source : https://www.cnblogs.com/-Wallace-/
     * Problem : Luogu P3157 CQOI2011 动态逆序对 / UVA 11990 ``Dynamic'' Inversion
     */
    #include <algorithm>
    #include <cstdio>
    
    using namespace std;
    const int N = 2e5 + 5;
    
    int n, m;
    struct bryidxt {
    	int t[N];
    	#define lbt(x) (x & (-x))
    	inline void inc(int p, int v) {
    		for (; p <= n + 1; p += lbt(p)) t[p] += v;
    	}
    	inline int get(int p) {
    		int ret = 0;
    		for (; p; p -= lbt(p)) ret += t[p];
    		return ret;
    	}
    } bit;
    
    struct query {
    	int val, delt, ans;
    } qry[N];
    int loc[N];
    
    inline bool cmp_delt(const query& x, const query& y) {
    	return x.delt < y.delt;
    }
    inline bool cmp_val(const query& x, const query& y) {
    	return x.val < y.val;
    }
    
    void cdqSolve(int l, int r) {
    	if (l == r) return;
    	
    	int mid = (l + r) >> 1;
    	cdqSolve(l, mid);
    	cdqSolve(mid + 1, r);
    	
    	int i = l, j = mid + 1;
    	for (; i <= mid; i++) {
    		for (; j <= r && qry[i].val > qry[j].val; j++)
    			bit.inc(qry[j].delt, 1);
    		qry[i].ans += bit.get(m + 1) - bit.get(qry[i].delt);
    	}
    	for (i = mid + 1; i < j; i++)
    		bit.inc(qry[i].delt, -1);
    	
    	i = r, j = mid;
    	for (; i > mid; i--) {
    		for (; j >= l && qry[i].val < qry[j].val; j--)
    			bit.inc(qry[j].delt, 1);
    		qry[i].ans += bit.get(m + 1) - bit.get(qry[i].delt);
    	}
    	for (i = mid; i > j; i--)
    		bit.inc(qry[i].delt, -1);
    	
    	inplace_merge(qry + l, qry + mid + 1, qry + r + 1, cmp_val);
    }
    
    signed main() {
    	while (scanf("%d%d", &n, &m) != EOF) {
    		fill(loc + 1, loc + 1 + n, 0);
    		fill(qry + 1, qry + 1 + n, query());
    		
    		for (register int i = 1; i <= n; i++) {
    			scanf("%d", &qry[i].val);
    			qry[i].delt = m + 1;
    			loc[qry[i].val] = i;
    		}
    		for (register int i = 1, p; i <= m; i++) {
    			scanf("%d", &p);
    			qry[loc[p]].delt = i;
    		}
    		
    		long long cnt = 0ll;
    		for (register int i = 1; i <= n; i++) {
    			cnt += bit.get(n) - bit.get(qry[i].val);
    			bit.inc(qry[i].val, 1);
    		}
    		for (register int i = 1; i <= n; i++)
    			bit.inc(qry[i].val, -1);
    		
    		cdqSolve(1, n);
    		
    		sort(qry + 1, qry + 1 + n, cmp_delt);
    		for (register int i = 1; i <= m; i++) {
    			printf("%lld
    ", cnt);
    			cnt -= qry[i].ans;
    		}	
    	}
    	return 0;
    }
    

    Solution 2

    树状数组套动态开点权值线段树(带修主席树)

    这玩意就不这么复杂了啊。首先还是计算好原先的逆序对数。

    差不多就是,当一个数 (a_i) 被删去时,逆序对数应减去,区间 ([1, i)) 中处在值域 ((a_i, n]) 的数的个数,以及区间 ((i, n]) 中处在值域 ([1, a_i)) 的数的个数。

    分开来算的话,要写两个冗长的函数,但其实可以打一个记号,就像上面一样,这样就能写成一个了。常数比较大,于是跑不过cdq。

    • 时间复杂度:(O((n + m)log^2 n))
    • 空间复杂度:(O((n + m)log^2 n))
    • 是否在线:是

    Code for Solution 2

    /*
     * Author : _Wallace_
     * Source : https://www.cnblogs.com/-Wallace-/
     * Problem : Luogu P3157 CQOI2011 动态逆序对 / UVA 11990 ``Dynamic'' Inversion
     */
    #include <cstdio>
    
    using namespace std;
    const int N = 2e5 + 5;
    const int S = N << 7;
    #define lbt(x) (x & (-x))
    #define mid ((l + r) >> 1)
    
    int n, m;
    int val[N], loc[N];
    long long cnt = 0ll;
    
    int root[N];
    int lc[S], rc[S];
    int size[S];
    int total = 0;
    
    void modify(int& x, int l, int r, int p, int v) {
    	if (!x) x = ++total;
    	size[x] += v;
    	if (l == r) return;
    	if (p <= mid) modify(lc[x], l, mid, p, v);
    	else modify(rc[x], mid + 1, r, p, v);
    }
    
    int node[2][N];
    inline int get(int l, int r, int x, bool tag) {
    	int cnt[2] = {0, 0}, ret = 0;
    	for (register int p = r; p; p -= lbt(p)) node[0][++cnt[0]] = root[p];
    	for (register int p = l - 1; p; p -= lbt(p)) node[1][++cnt[1]] = root[p];
    	
    	for (l = 1, r = n; l != r; )
    		if (x > mid) {
    			if (tag) {
    				for (register int i = 1; i <= cnt[0]; i++) ret += size[lc[node[0][i]]];
    				for (register int i = 1; i <= cnt[1]; i++) ret -= size[lc[node[1][i]]];
    			}
    			for (register int i = 1; i <= cnt[0]; i++) node[0][i] = rc[node[0][i]];
    			for (register int i = 1; i <= cnt[1]; i++) node[1][i] = rc[node[1][i]];
    			l = mid + 1;
    		} else {
    			if (!tag) {
    				for (register int i = 1; i <= cnt[0]; i++) ret += size[rc[node[0][i]]];
    				for (register int i = 1; i <= cnt[1]; i++) ret -= size[rc[node[1][i]]];
    			}
    			for (register int i = 1; i <= cnt[0]; i++) node[0][i] = lc[node[0][i]];
    			for (register int i = 1; i <= cnt[1]; i++) node[1][i] = lc[node[1][i]];
    			r = mid;
    		}
    	return ret;
    }
    
    void clear(int x) {
    	if (!x) return;
    	clear(lc[x]), clear(rc[x]);
    	lc[x] = rc[x] = size[x] = 0;
    }
    
    signed main() {
    	while (scanf("%d%d", &n, &m) != EOF) {
    		for (register int i = 1; i <= n; i++)
    			clear(root[i]), root[i] = 0, loc[i] = val[i] = 0;
    		total = cnt = 0;
    		
    		for (register int i = 1; i <= n; i++) {
    			scanf("%d", val + i), loc[val[i]] = i;
    			cnt += get(1, i - 1, val[i], 0);
    			for (register int p = i; p <= n; p += lbt(p))
    				modify(root[p], 1, n, val[i], 1);
    		}
    		
    		for (register int x; m; --m) {
    			scanf("%d", &x);
    			printf("%lld
    ", cnt);
    			cnt -= get(1, loc[x] - 1, x, 0);
    			cnt -= get(loc[x] + 1, n, x, 1);
    			for (register int p = loc[x]; p <= n; p += lbt(p))
    				modify(root[p], 1, n, x, -1);
    		}	
    	}
    	return 0;
    }
    

    Solution 3

    线段树套平衡树

    另一种树套树方法,套路和 Solution 2 基本一样。

    常数大一点,空间小一点。做法这里不做赘述。

    • 时间复杂度:(O((n + m)log^2 n))
    • 空间复杂度:(O(nlog n))
    • 是否在线:是

    Code 不想写了 /kel 可以参考一下:https://www.luogu.com.cn/blog/van/solution-uva11990

    Solution 4

    K-D Tree

    既然可以转化成三维数点问题然后 cdq,那么K-D Tree自然也是可以的。

    先把树建好,然后一个个删除。(当然你也可以离线然后倒着操作换成插入)

    考虑到是惰性删除,所以可以定期重构来优化。一般来说因为常数因子的影响,其实跑得比大多数树套树快。

    • 时间复杂度:(O(nlog n + mn^{frac{2}{3}}))
    • 空间复杂度:(O(n))
    • 是否在线:是

    Solution 5

    数列分块套树状数组。 块乐

    先分个块,每个块维护一个树状数组。删除数值 (x) 时,在每个块的树状数组中 (x) 的位置减去一,然后答案动态维护。

    如果常数小可以过。

    • 时间复杂度:(O(msqrt{n}log n))(块长为 (sqrt{n}) 时,但取 (10^3) 差不多效率可以更高)
    • 空间复杂度:(O(nsqrt{n}))
    • 是否在线:是
  • 相关阅读:
    js图片放大
    js编写点名器
    javascript中的math和随机数
    python中 __slots__
    python中 @property
    CentOS 6.5通过yum安装 MySQL-5.5
    linux下环境搭建
    oracle:ORA-01940无法删除当前已连接用户的解决方案
    不同版本apache(免安装)下部署Javaee多套项目
    使用poi处理excel
  • 原文地址:https://www.cnblogs.com/-Wallace-/p/12883401.html
Copyright © 2020-2023  润新知