『CDQ分治·树状数组』[CQOI2011]动态逆序对

题目描述

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

题解

这道题的主要思路就是求出总的逆序对,然后再去掉某个数时删去这个数带来的贡献。

一个数若要对逆序对产生贡献,必须满足一下的两种情况:

  • 如果这个数是较小的数,则贡献就是位置比它小,数值比它大,且消失时间比它晚点的个数。
  • 如果这个数是较大的数,则贡献就是位置比它大,数值比它小,且消失时间比它晚点的个数。

这正好给了三个对应量的大小关系,是一个三维偏序问题。使用CDQ分治+树状数组即可求出每一份点的贡献。

代码如下(代码其实很短的):

#include <bits/stdc++.h>

#define int long long

using namespace std;

inline int read(void)
{
	int s = 0;char c = getchar();
	while (c<'0' || c>'9') c = getchar();
	while (c>='0' && c<='9') s = s*10+c-48,c = getchar();
	return s;
}

struct node
{
	int v,num,t,pos,ans;
	friend bool operator < (node p1,node p2)
	{
		return p1.pos > p2.pos;
	}
} ;

int n,m,ans;
int v[1000000];
node a[1000000];
node t[1000000];

struct TREE
{
	int S[1000000];
	#define lowbit(i) (i&-i)
	void add(int x,int v)
	{
		for (int i=x;i<=n;i+=lowbit(i))
		    S[i] += v;
		return;
	}
	int ask(int x)
	{
		int sum = 0;
		for (int i=x;i>=1;i-=lowbit(i)) 
		    sum += S[i];
		return sum;
	}
} tree ,infc ; 

void Readnum(void)
{
	n = read(),
	m = read();
	for (int i=1;i<=n;++i)
	{
		a[i].v = read();
		a[i].num = i; 
		a[i].t = m+1; 
		a[i].pos = i;
		v[a[i].v] = i;  
	}
	for (int i=1;i<=m;++i)
	    a[v[read()]].t = i;
	return;
}

void Cdq1(int l,int r)
{
	int mid = l+r >> 1;
	if (l == r) return;
	Cdq1(l,mid);
	Cdq1(mid+1,r);
	int h1 = l, h2 = mid+1,p = l-1;
	while (h1 <= mid && h2 <= r)
	{
		if (a[h1].v > a[h2].v) 
		{
			tree.add(a[h1].t,1);
			t[++p] = a[h1++];
		}
		if (a[h1].v < a[h2].v)
		{
			a[h2].ans += tree.ask(n)-tree.ask(a[h2].t);
			t[++p] = a[h2++];
		}
	}
	while (h1 <= mid) 
	{
		tree.add(a[h1].t,1);
		t[++p] = a[h1++];
	}
	while (h2 <= r) 
	{
		a[h2].ans += tree.ask(n)-tree.ask(a[h2].t);
		t[++p] = a[h2++];
	}
	for (int i=l;i<=mid;++i) tree.add(a[i].t,-1);
	for (int i=l;i<=r;++i) a[i] = t[i];
	return;
} 

void Get_ans(void)
{
	ans = 0;
	for (int i=n;i;--i)
	{
		ans += infc.ask(a[i].v);
		infc.add(a[i].v,1);
	}
	return;
}

void Cdq2(int l,int r)
{
	int mid = l+r >> 1;
	if (l == r) return;
	Cdq2(l,mid);
	Cdq2(mid+1,r);
	int h1 = l, h2 = mid+1,p = l-1;
	while (h1 <= mid && h2 <= r)
	{
		if (a[h1].v < a[h2].v)
		{
			tree.add(a[h1].t,1);
			t[++p] = a[h1++];
		}
		if (a[h1].v > a[h2].v)
		{
			a[h2].ans += tree.ask(n)-tree.ask(a[h2].t);
			t[++p] = a[h2++];
		}
	}
	while (h1 <= mid) 
	{
		tree.add(a[h1].t,1);
		t[++p] = a[h1++];
	}
	while (h2 <= r)
	{
		a[h2].ans += tree.ask(n)-tree.ask(a[h2].t);
		t[++p] = a[h2++];
	}
	for (int i=l;i<=mid;++i) tree.add(a[i].t,-1);
	for (int i=l;i<=r;++i) a[i] = t[i];
	return;
}

bool cmp(node p1,node p2)
{
	return p1.t<p2.t;
}

void output(void)
{
	sort(a+1,a+n+1,cmp);
	for (int i=1;i<=m;++i)
	{
		printf("%lld\n",ans);
		ans -= a[i].ans;
	}
	return;
}

signed main(void)
{
	freopen("inverse.in","r",stdin);
	freopen("inverse.out","w",stdout);
	Readnum();
	Get_ans();
	Cdq1(1,n);
	sort(a+1,a+n+1); 
	Cdq2(1,n);
	output();
	return 0;
} 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值