洛谷P3157 : 动态逆序对 (cdq分治)

题目大意:对于序列A,它的逆序对数定义为满足i<j,且Ai>Aj的数对(i,j)的个数。给1到n的一个排列,按照某种顺序依次删除m个元素,你的任务是在每次删除一个元素之前统计整个序列的逆序对数。

分析:这题是树状数组套主席树水题,按顺序加入数字,求出最后的逆序对数,删除数字时只需要删除该数字对答案的贡献,该数字的贡献有两种:位置在它前面值大于它 和 位置在它后面值小于它。用主席树这个东西很好求,其中后一种用区间减法可以求得。删除掉该数字后更新一下主席树即可。
主席树题解:https://blog.csdn.net/qq_41997978/article/details/89442123

cdq分治解法:初学cdq分治,我们也可以用cdq分治在离线搞掉这题,常数小,时间和空间都优于树套树。

cdq分治的基本思想:cdq分治主要用来解决偏向类问题,也可以解决查询-修改操作的数据结构问题,当问题能转化为偏序类问题,且支持离线操作时可以使用该算法,网上介绍cdq分治的博客非常多。

cdq分治的基本思想在于:先递归求解左子区间和右子区间的问题,再求出左区间操作对右区间答案的贡献,过程类似于归并排序,可以回忆一下归并排序求逆序对的过程。cdq分治在应对不同情况时有不同的写法,这里蒟蒻还没完全掌握。。。暂时不讨论,后期再补上。

不懂cdq分治的同学可以先学一下cdq分治。

具体做法:原问题删除数字可以转化为插入数字,这样第一个删除的数字变成最后一个插入的数字, 每个插入数字的操作都有一个时间。操作结构体有时间,位置,值三个属性,分别为t,pos,val。问题可以转化为求出所有 ti < t, p < pos , v > val 的有序对,答案可以由每一个操作单独的贡献求和得到,而每一个操作单独的贡献显然就是一个三维偏向问题:求出所有 ti < t, p < pos , v > val 个数。

事实上逆序对还有有两种,另一种是ti < t,p > pos,v < val,但仔细一想似乎在普通的逆序对问题只考虑了上面一种情况?这是因为我们可以按插入顺序依次求解每个刚插入的数值得到的贡献,最后求和,因为此时它后面肯定还没有插入数字,可以不重复的求出所有答案。

但这里将删除操作改成了插入操作,情况就变了。

如果没有将删除改成插入,原序列插入时间ti < t的话,插入位置p 一定 < pos (想一想,为什么)
但这里因为我们将删除变成了插入,导致一些数字的操作时间改变,而它们的位置却不变。

如果还按只考虑一种情况的方法来求逆序对,显然会漏解,因为有的数字插入后,它的后面是存在数字的,换句话说不再是完全按位置顺序依次插入了。

因此求解答案要考虑两种情况,操作先按插入时间排序,cdq分治按pos进行排序,用权值树状数组在log时间内求出v < val的值。在求出一种情况后,要将树状数组清除,再求第二种情况。最后还要将树状数组清除。

虽然看起来是每个数字都计算两种逆序对,但只有操作改变的那些数字才会有两种贡献(想一想,为什么)

看代码可以好好理解一下。

#include<bits/stdc++.h>
using namespace std;
#define lowbit(i) (i & (-i))
const int maxn = 5e5 + 10;
int n,m;
int top;
struct node {
	long long t,pos,val;
	bool operator < (const node & rhs) const {
		if(pos == rhs.pos) return val < rhs.val;
		return pos < rhs.pos;
	}
}q[maxn * 3],tmp[maxn];
long long mp[maxn],a[maxn],ans[maxn];
long long sum[maxn];
long long getsum(int p) {
	long long ans = 0;
	for(int i = p; i; i -= lowbit(i)) ans += sum[i];
	return ans;
}
void modify(int p,int v) {
	for(int i = p; i <= n; i += lowbit(i)) sum[i] += v;
}
bool cmp(node a,node b) {
	return a.t < b.t;
}
void cdq(int l,int r) {
	if(l == r) return;
	int mid = l + r >> 1;
	cdq(l,mid);cdq(mid + 1,r);
	top = 0;
	for(int i = l,j = mid + 1; i <= mid || j <= r;) {
		if(i <= mid && (j > r || q[i] < q[j])) {
			modify(q[i].val,1);
			tmp[++top] = q[i++];
		}
		else {
			ans[q[j].t] += getsum(n) - getsum(q[j].val);
			tmp[++top] = q[j++];
		}
	}
	for(int i = l; i <= mid; i++) {
		modify(q[i].val,-1);
	}
	for(int i = l; i <= r; i++) {
		q[i] = tmp[i - l + 1];
	}
	for(int i = r; i >= l; i--) {
		if(q[i].t <= mid) modify(q[i].val,1);
		else ans[q[i].t] += getsum(q[i].val);
	}
	for(int i = r; i >= l; i--)
		if(q[i].t <= mid) modify(q[i].val,-1);
	
}
int main() {
	scanf("%d%d",&n,&m);
	int x,t = n;
	for(int i = 1; i <= n; i++) {
		scanf("%lld",&a[i]);
		q[i] = (node) {0,i,a[i]};
		mp[a[i]] = i;
	}
	for(int i = 1; i <= m; i++) {
		scanf("%d",&x);
		q[mp[x]].t = t--;
	}
	for(int i = 1; i <= n; i++) {
		if(!q[i].t) q[i].t = t--;
	}
	sort(q + 1,q + n + 1,cmp);
	cdq(1,n);
	for(int i = 1; i <= n; i++) ans[i] += ans[i - 1];
	for(int i = n; i > n - m; i--) {
		printf("%lld\n",ans[i]);
	}
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值