整体二分&CDQ分治:[BZOJ2527][POI2011] meteors [BZOJ3295][CQOI2011] 动态逆序对

首先我们先说说3295题的树套树做法:显然只需要求出每次修改x左边比自身大的数的个数和右边比自身小的个数,就是x的贡献,每次减去贡献再删除x即可,这样就相当于带修改的可持久化线段树就能解决问题。显然,我们用树状数组套权值线段树来解决这个问题,每次修改就相当于修改棵可持久化线段树上的一个权值,所以在修改上总的时间复杂度是,下面来考虑查询。

我们每次查询显然可以分成段,这样我们显然可以来完成所有的查询。

然后考虑空间问题。采用动态开点的方法可以将总的空间复杂度压缩到级别,这是由于每个点至多影响个点,这样我们可以仅仅开3000万个数的空间。如果你还是怕出问题,可以用一个unsigned short和一个char来压8位的方法解决问题。总时间复杂度

这样我们就能解决这个问题了。


那么用CDQ分治怎么解决这个问题呢?首先我们考虑每个数对逆序对的贡献f[],那么我们可以猜想下一次的答案nextans满足: nextans=ans-f[i] 但是显然我们可以发现这个猜想是错误的,因为删除的数可能贡献了重复的逆序对,因此我们只要求出删除的数按照原位置的逆序对组数就可以了。

那么我们怎么求这个组数呢。首先,若x[],y[],pos[]分别代表一个数的值,位置和删除时间,g[i]代表加入删去第i个数后逆序对增加的数量,那么有

显然我们仅仅按pos排序是搞不出什么名堂的qaq,因为是二维,所以不好处理(似乎这里又可以回归树套树套路),这时我们可以考虑cdq分治套路,递归将询问按y值排序,这样我们用分治法,先求出仅在i,j<=mid的答案,再求出i,j>mid的答案,然后求出i<mid,j<mid的答案即可。

于是我们可以树状数组维护一下,按照y值顺序依次加入,然后依次query一下就可以了(细节我就不说了),于是我们愉快地艹掉了一维,时间复杂度

具体可以看代码,写了无数的注释之后发现我zz地把归并写错辣

#include"bits/stdc++.h"
using namespace std;

const int N=300005;
typedef long long ll;
const int T=1500005;
char _buff[T];int _pos;
void read(){fread(_buff,T,1,stdin);}
#define gch _buff[_pos++]
template<class integer>
inline void read(integer&x){
	x=0;int f=1;char ch=gch;
	while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=gch;}
	while(ch<='9'&&ch>='0'){x=x*10+ch-'0';ch=gch;}
	x*=f;
}

#define lowbit(x) ((x)&-(x))
int a[N],q[N],n,m;
int sum[N],f[N],g[N],num[N];
ll ans;

inline void add(int x,int u){
	for(num[x]+=u;x<=n;x+=lowbit(x))
		sum[x]+=u;
}
inline int qry(int l,int r){
	int re=0;
	while(r>=l){
		for(;r-lowbit(r)+1>=l && r;r-=lowbit(r))
			re+=sum[r];
		if(r>=l) re+=num[r--];
	}
	return re;
}

void init(){
	read();read(n),read(m);int i;
	for(i=1;i<=n;i++)read(a[i]);for(i=1;i<=m;i++)read(q[i]);
	for(i=1;i<=n;i++)f[i]=qry(a[i]+1,n),add(a[i],1);
	for(i=1;i<=n;i++)add(a[i],-1),f[i]+=qry(1,a[i]-1);
}

int qu[N],b[N];
//计算每次增加数时的逆序对总数:
//每个数有三个属性:值 位置 问题时间 
//即 a[qu[]] qu[] pos 用x,y,z表示
//则i和j满足逆序对的充分必要条件为 (xi-xj) * (yi-yj) < 0 此时在max(zi,zj)会构成逆序对 

void solve(int l,int r){
	if(l==r){g[qu[l]]=0;return;}
	//当l==r说明无法分治,显然这一段不存在逆序对 
	int mid=(l+r)>>1,i,j;
	solve(l,mid);solve(mid+1,r);
	//分治处理i和j均在左边,均在右边的情况 
	//假设j在左侧,i在右侧 
	
	//处理qu[j]<qu[i] a[qu[j]]>a[qu[i]]的情况 
	for (j=l,i=mid+1; i<=r; i++){ //枚举右侧每一个值 
		while (j<=mid && qu[j]<qu[i])
			add(a[qu[j]],1),j++; //由于按照qu排序,处理qu[j]<qu[i]的情况 
		g[qu[i]] += qry(a[qu[i]]+1,n); //求这种情况的总数 
	}
	while (j<=mid) add(a[qu[j]],1),j++; //加上剩余部分的值即加上了所有左侧值 
	
	//处理qu[j]>qu[i] a[qu[j]]<a[qu[i]]的情况 
	for (j=l,i=mid+1; i<=r; i++){ //仍然枚举右侧每一个值 
		while (j<=mid && qu[j]<qu[i])
			add(a[qu[j]],-1),j++; //删除qu[j]<qu[i]的情况 
		g[qu[i]] += qry(1,a[qu[i]]-1); //求这种情况的总数 
	}
	while (j<=mid) add(a[qu[j]],-1),j++; //减去剩余的情况 
	
	for (j=l,i=mid+1; i<=r||j<=mid; ){
		if (j<=mid && (i>r || qu[j]<qu[i])) b[i-mid+j-1]=qu[j],j++;
		else b[i-mid+j-1]=qu[i],i++;
	}
	for (i=l; i<=r; i++)
		qu[i] = b[i];
}

int p[N];

int main(){
	init(); int i;
	for(i=1;i<=n;i++)p[a[i]]=i;
	for(i=1;i<=m;i++)qu[i]=p[q[i]];
	solve(1,m);
	for(i=1;i<=n;i++)ans+=f[i];
	for(i=1,ans>>=1;i<=m;i++){
		printf ("%lld\n",ans);
		ans += - f[p[q[i]]] + g[p[q[i]]];
	}
	return 0;
}


整体二分感觉和CDQ分治其实差不多,对于2527这道题,显然如果对于单个的国家我们可以二分答案然后求值,但是如果这样的话时间复杂度就会愉快地达到级别,显然作死。但是我们同样可以利用分治思想整体处理问题,首先我们将答案>mid和<=mid的分成两部分,显然>mid的一部分可以借用之前的判断答案与mid关系时计算出的值。那么这样分治下去的复杂度是多少呢?我们来分析一下。

我们分层考虑,对于每一层都有n个站,共有logn层。每一层进行的操作的次数都是级别的,那么我们的时间复杂度就有

#include"bits/stdc++.h"
using namespace std;

const int N=300005;
typedef long long ll;
const int T=40000005;
char _buff[T];int _pos;
void read(){fread(_buff,T,1,stdin);}
#define gch _buff[_pos++]
template<class integer>
inline void read(integer&x){
	x=0;int f=1;char ch=gch;
	while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=gch;}
	while(ch<='9'&&ch>='0'){x=x*10+ch-'0';ch=gch;}
	x*=f;
}

int L[N*2],R[N*2],val[N*2],id[N],ex[N],ans[N],n,m,q;
int h[N],nxt[N];ll add[N],num[N],sum[N];

void init(){
	read();read(m),read(n);
	int i;for(i=1;i<=n;i++)read(id[i]);
	for(i=1;i<=m;i++)read(ex[i]);read(q);
	for(i=1;i<=q;i++)read(L[i]),read(R[i]),read(val[i]);
	for(i=1;i<=n;i++)nxt[i]=h[id[i]],h[id[i]]=i;
}

int qu[N],done;
ll tmp[N];

#define lowbit(x) ((x)&-(x))

void up(int l,int r,int u){
	while (r>=l){
		num[r]+=u; r--;
		for (; r-lowbit(r)+1>=l && r; r-=lowbit(r))
			add[r]+=u;
	}
}

void clear(int del_first){
	while (done >= del_first){
		if (L[done] <= R[done])
			up (L[done], R[done], -val[done]);
		else up (L[done], n, -val[done]),
			up (1, R[done], -val[done]);
		done -- ;
	}
}

ll get(int x){
	ll re = num[x];
	for (; x<=n; x+=lowbit(x))
		re += add[x];
	return re;
}

void work(int l,int r,int&d,int ncase){
	int i,j;
	while(done<min(ncase,q)){
		done ++ ;
		if (L[done] <= R[done])
			up (L[done], R[done], val[done]);
		else up (L[done], n, val[done]),
			up (1, R[done], val[done]);
	}
	for (i=l; i<=r; i++){
		for (j=h[qu[i]]; j; j=nxt[j]){
			tmp[qu[i]] += get(j);
			if(tmp[qu[i]] > 1e18)
				break;
		}
	}
	for (i=j=l; j<=r; j++)
		if (tmp[qu[j]] >= ex[qu[j]])
			swap(qu[i],qu[j]),i++;
	d=i-1;
	for (i=l; i<=r; i++) tmp[qu[i]]=0;
}

void solve(int s,int t,int l,int r){
	if(l==r){for(int i=s;i<=t;i++)ans[qu[i]]=l;return;}
	int mid=(l+r)>>1,p,i;work(s,t,p,mid);
	if(p<t)solve(p+1,t,mid+1,r);clear(l);
	if(s<=p)solve(s,p,l,mid);
}

int main(){
	init();int i;for(i=1;i<=m;i++)qu[i]=i;solve(1,m,1,q+1);
	for(i=1;i<=m;i++)ans[i]>q?puts("NIE"):printf("%d\n",ans[i]);
	return 0;
}


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值