bzoj3295 动态逆序对(CDQ分治)

题目链接:https://www.lydsy.com/JudgeOnline/problem.php?id=3295

                                     3295: [Cqoi2011]动态逆序对

                                                             Time Limit: 10 Sec  Memory Limit: 128 MB
                                                                     Submit: 7178  Solved: 2548
                                                                       [Submit][Status][Discuss]

Description

对于序列A,它的逆序对数定义为满足i<j,且Ai>Aj的数对(i,j)的个数。给1到n的一个排列,按照某种顺序依次删

除m个元素,你的任务是在每次删除一个元素之前统计整个序列的逆序对数

Input

输入第一行包含两个整数n和m,即初始元素的个数和删除的元素个数。

以下n行每行包含一个1到n之间的正整数,即初始排列。

以下m行每行一个正整数,依次为每次删除的元素。

N<=100000 M<=50000

Output

输出包含m行,依次为删除每个元素之前,逆序对的个数。

Sample Input

5 4
1
5
3
4
2
5
1
4
2

Sample Output

5
2
2
1

 

思路:这是一道十分经典的题目。可以用CDQ分治做,也可以用树套树做。不过做树套树身体会被掏空的,还是CDQ好了。。。

之前一直不太了解CDQ分治,虽然听说过它的威名,却一直没能学会。今天突击了一下,算是刚入了点门吧(可能连门都没入) 

如同网上大佬们所说的,一般三维的CDQ问题,第一位排序,第二维分治,第三位数据结构(BIT)。。。

本蒟蒻就照着这个做了。不过之前一直没能理解别人的代码中额外开一个数组记录是什么意思,后来在汪聚聚的讲解下明白了这是归并操作(话说我是不是学了个假的分治。。。)

这道题具体思路可以看: http://www.cnblogs.com/lidaxin/p/5255440.html

主要思想就是将每个点抽象成一个三元组(位置,数,时间戳)。而上面的做法的巧妙之处在于将删点转化为加点(时间戳反向就行了)。所以这道题变成了第一位排序时间戳,第二维CDQ位置,第三维用BIT位数它前面的数。由于每个点要统计前面和后面的点,所以要跑两次CDQ。。。

代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAXN=1e5+10;
typedef struct
{
	ll x;
	ll p;
	ll T;
}  C;
C c[MAXN];
int pos[MAXN];
C res[MAXN];
ll tot=0;
ll mx;
int n,m;
bool cmp1(C a, C b)
{
	return a.T<b.T;
}
bool cmp2(C a,C b)
{
	return a.p<b.p;
}
ll sum[MAXN];
ll ans[MAXN];
void add(int p, ll x){ 
    while(p) sum[p] += x, p -= p & -p;
}
ll ask(int p){ 
    ll res = 0;
    while(p <= mx) res += sum[p], p += p & -p;
    return res;
}
void solve(int l,int r)
{
	if(l==r)
	return ;
	int m=(l+r)>>1;
	solve(l,m);
	solve(m+1,r);
	int ptr=l;
	tot=l-1;
	for(int i=m+1;i<=r;i++)
	 {
	 	   while(ptr<=m&&c[ptr].p<c[i].p)
	 	   {
		      add(c[ptr].x,1);
		      res[++tot]=c[ptr];
		      ptr++;
		   }
		   res[++tot]=c[i];
		   ans[c[i].T]+=ask(c[i].x+1);
	 }
	 for(int i=l;i<ptr;i++)
	 {
	 	 add(c[i].x,-1);
	 }
	 for(int i=ptr;i<=m;i++)
	 res[++tot]=c[i];
	 for(int i=l;i<=r;i++)
	 {
	 	  c[i]=res[i];
	 }
}
int main()
{
	ll tem;
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
	{
		scanf("%lld",&c[i].x);
		c[i].p=i;
		pos[c[i].x]=i;
		mx=max(mx,c[i].x);
	}
	for(int i=1;i<=m;i++)
	{
		 scanf("%lld",&tem);
		 c[pos[tem]].T=m-i+1;
	}
	sort(c+1,c+1+n,cmp1);
	solve(1,n);
	for (int i=1;i<=n;++i) 
    {
	     c[i].p*=-1;
		 c[i].x=n-c[i].x+1;
	}
    sort(c+1,c+1+n,cmp1);
    solve(1,n);
    for(int i=1;i<=n;i++)
    {
    	 ans[i]+=ans[i-1];
	}
	for(int i=1;i<=m;i++)
	{
		 printf("%lld\n",ans[m-i+1]);
	}
	return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值