BZOJ3295 动态逆序对 CDQ/分块+树状数组

题意:

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

输入:

输入第一行包含两个整数 n 和 m ,即初始元素的个数和删除的元素个数。以下 n 行每行包含一个 1 到 n 之间的正整数,即初始排列。以下 m 行每行一个正整数,依次为每次删除的元素。

输出:

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

解法:

思路一:CDQ分治+树状数组

首先题目中是包含删除与查询操作,开始想法是反着来一个个加进去在计算贡献,但实际上可以直接正着算,考虑删除时每个点产生的影响。

计算逆序对常用的方法是对于一个值用树状数组查询在这个值之前比它大的数的个数和之后小的数的个数。

因此在这里我们可以考虑用点对\((x,y)\)表示在x位加入一个值为y的点,每一次加入一个点的时候我们需要的就是\([1,x-1]\)\(val>y\)的数的个数。

由于操作有时间的先后顺序,且还要注意下标的范围,因此可以考虑用CDQ分治。

附代码加注释:

#include<bits/stdc++.h>
#define M 100005
#define ll long long
#define lowbit(x) (x&-x)
using namespace std;
int n,m,a[M],pos[M],del[M];
long long ans[M];
struct node {
	int t,id,pos,f;
	bool operator<(const node&_)const {
		if(pos!=_.pos)return pos<_.pos;//根据下标x排序
		return f>_.f;
	}
} A[M<<1];
struct Tree {
	ll cnt[M];
	void add(int x,int v) {
		while(x)cnt[x]+=v,x-=lowbit(x);
	}
	int sum(int x) {
		int res=0;
		while(x<=n)res+=cnt[x],x+=lowbit(x);
		return res;
	}
} T;
void CDQ(int l,int r) {
//	printf("%d %d\n",l,r);
	if(l>=r)return;
	int mid=(l+r)>>1;
	CDQ(l,mid),CDQ(mid+1,r);//先处理下面的,防止下面排序的操作产生影响
	sort(A+l,A+r+1);
	int tot=0;
	int now=0;
	for(int i=l; i<=r; i++) {//正着找大的,反着找小的
		if(A[i].t<=mid)T.add(a[A[i].pos],A[i].f);
		else ans[A[i].id]+=A[i].f*T.sum(a[A[i].pos]+1);
	}
	for(int i=l; i<=r; i++)if(A[i].t<=mid)T.add(a[A[i].pos],-A[i].f);
	for(int i=r; i>=l; i--) {
		if(A[i].t<=mid)T.add(a[A[i].pos],A[i].f);
		else ans[A[i].id]+=A[i].f*(T.sum(1)-T.sum(a[A[i].pos]));
	}
	for(int i=l; i<=r; i++)if(A[i].t<=mid)T.add(a[A[i].pos],-A[i].f);
}
int main() {
	scanf("%d%d",&n,&m);
	int sz=0;
	for(int i=1; i<=n; i++)scanf("%d",&a[i]),pos[a[i]]=i,del[a[i]]=0;//因为开始全部加入,所以用一个较小的0
	for(int i=1; i<=n; i++)A[++sz]=(node)<%sz,0,pos[a[i]],1%>;//加入为1 删除为0
	for(int i=1,x; i<=m; i++)scanf("%d",&x),A[++sz]=(node)<%sz,i,pos[x],-1%>;
	CDQ(1,sz);
	for(int i=1; i<=m; i++)ans[i]+=ans[i-1],printf("%lld\n",ans[i-1]);
	return 0;
}

思路二:分块+树状数组

分块查询每个块中大于当前值的个数以及后面小于该值的数的个数

因为更新是+所以查询比它大的值的个数是\(sum(pos[x],n)-sum(pos[x],x)\)

然后删除的时候要同时减去之前的和之后的

若块的大小为s

查询块的复杂度为log(s)//树状数组的复杂度

单个查询的复杂度为s 复杂度为\((\frac{n}{s} *\log n)\)

所以,块的大小定为\(\sqrt{n*\log n}\)

#include<bits/stdc++.h>
using namespace std;
#define lowbit(x) (x&-x)
#define M 100005
int n,m,a[M],b[M],cnt[90][M],id[M];
long long tot[M],ans=0;
bool mark[M];
void add(int x,int k,int q) {
    while(x<=n)cnt[k][x]+=q,x+=lowbit(x);
}
int sum(int x,int k) {
    int res=0;
    while(x)res+=cnt[k][x],x-=lowbit(x);
    return res;
}
int main() {
    scanf("%d%d",&n,&m);
    memset(cnt,0,sizeof(cnt));
    int s=sqrt(n*log(n)/log(2));
    for(int i=1; i<=n; i++)scanf("%d",&a[i]),id[a[i]]=i;
    for(int i=1; i<=m; i++)scanf("%d",&b[i]);
    for(int i=1; i<=n; i++) {
        for(int j=i/s*s; j<i; j++)ans+=(a[j]>a[i]);
        for(int j=0; j<i/s; j++)ans+=sum(n,j)-sum(a[i],j);
        add(a[i],i/s,1);
    }
    for(int i=1; i<=m; i++) {
        int x=b[i];
        x=id[b[i]];
        printf("%lld\n",ans);
        for(int j=x+1; j<=min((x/s+1)*s-1,n); j++)ans-=(!mark[j])*(a[j]<a[x]);
        for(int j=x/s+1; j<=n/s; j++)ans-=sum(a[x],j);
        for(int j=x/s*s; j<x; j++)ans-=(!mark[j])*(a[j]>a[x]);
        for(int j=0; j<x/s; j++)ans-=sum(n,j)-sum(a[x],j);
        add(a[x],x/s,-1);
        mark[x]=1;
    }
    return 0;
}
posted @ 2019-06-05 18:41  季芊月  阅读(153)  评论(0编辑  收藏  举报