cdq分治

cdq分治主要用来解决三维偏序的关系,即给定n个点,每个点有x,y,z三个坐标。要求输出对于每个点,所有维数都比它小的点的个数。
对于一维偏序,直接排序即可。
对于二维偏序,排序一维,树状数组维护一维。
对于三维偏序,排序后分治一维,在分治的过程中,排序一维,树状数组维护一维。基本思想就是,先按x排序。要处理[1,n]的区间,先处理[1,mid]和[mid+1,n]的区间后,在合并这两个子问题时,需要算上左边区间对右边区间的点的贡献。这时候左区间的x的值必然小于右区间,所以我们再分别对这两个子区间对y排序。然后处理右区间的i时,要将左区间内所有y值小于其y值的点更新到树状数组中,然后计算i这个点的答案。

/*
cdq分治
复杂度:O(nlognlogk)  k为元素的大小,n为元素的个数 
*/ 

#include <iostream>
#include <algorithm> 
using namespace std;

struct node{
	int x,y,z;
	int id;
	bool operator<(const node&n)const   //默认第一维排序 
	{
		if( x == n.x )
		{
			if( y == n.y ) return z < n.z;
			return y < n.y;
		}
		return x < n.x;
	}
}p[100005];

int c[200005],real[200005],ans[200005];

int lowbit(int x)
{
	return x & -x;
}

int n,k;

void update(int x,int v)
{
	for (int i = x; i <= k; i += lowbit(i))
	{
		c[i] += v;
	}
}

int query(int x)
{
	int res = 0;
	for (int i = x; i > 0; i-= lowbit(i))
	{
		res += c[i];
	}
	return res;
}

bool cmp(node a,node b)  //按y排序 
{
	if( a.y == b.y ) return a.z < b.z;
	return a.y < b.y;
}

void cdq(int l,int r)   //cdq分治,处理[l,r] 
{
	if( l == r ) return;
	int mid = (l + r) / 2;   //计算中点 
	cdq(l,mid),cdq(mid+1,r); //分治计算左右区间 
	sort(p+l,p+1+mid,cmp);   //将左右区间按y排序 
	sort(p+1+mid,p+1+r,cmp);
	int i = mid + 1,j = l;
	for (; i <= r; i++)    //计算右区间的所有点 
	{
		while( p[j].y <= p[i].y && j <= mid )  //左区间内x小于i.x,y小于i.y的点更新到树状数组中 
		{
			update(p[j].z,1);
			j ++;
		}
		ans[p[i].id] += query(p[i].z);   //计算贡献 
	}
	for (i = l; i < j; i++)   //原路返回,加了多少就减去就多少 
	{
		update(p[i].z,-1);
	}
}

int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	cin >> n >> k; 
	for (int i = 1; i <= n; i++)
	{
		cin >> p[i].x >> p[i].y >> p[i].z;
		p[i].id = i;
	}
	sort(p+1,p+1+n);
	int pre = 0;
	for (int i = 1; i <= n; i++)   //考虑完全相同的点,那么这些点只要计算最靠右的点即可 
	{
		if( i == 1 ) pre = i;
		else
		{
			if( p[i].x != p[i-1].x || p[i].y != p[i-1].y || p[i].z != p[i-1].z )
			{
				for (int j = pre; j < i; j++) real[p[j].id] = p[i-1].id;
				pre = i;
			}
		}
		if( i == n )
		{
			for (int j = pre; j <= i; j++) real[p[j].id] = p[n].id;
		}
	}
	cdq(1,n);
	for (int i = 1; i <= n; i++)
	{
		cout << ans[real[i]] << '\n';   //有多少点小于第i点。 
	}
	return 0;
}

cdq分治还可以用来解决以二维偏序为基础上求一些值的问题,其实三维偏序就是在二维偏序的基础上,求第三维小于某个值的元素个数,算是一种简单特例。
例题:
有这样一个dp式子,dp[i]=dp[j]+(i-j) * (i-j-1)/2+a[i] 这里要求j<i且a[j]<=a[i],求dp[i]的极值。
这种有i*j的式子我们可以考虑斜率优化,-2 * dp[j] + j * j + j =2 * i * j + 2dp[i]- i * i+i+a[i]。当i确定后dp[i]- i * i+i+a[i]就确定了,所以我们只要求整个值尽可能大即可,斜率优化就可以做,但是j<i并且a[j]<a[i]则是一个二维偏序的限制,所以我们用cdq分治,先处理[l,mid],再处理左边对右边的影响,最后处理右边。

#include <iostream>
#include <algorithm>
using namespace std;

typedef long long ll;
const int maxn = 1e5+5;
ll dp[maxn],q[maxn];

struct node{
	ll id,val;
	bool operator<(const node&n)const
	{
		if( val == n.val ) return id < n.id;
		return val < n.val;
	} 
}a[maxn];

bool cmp1(node a,node b)
{
	return a.id < b.id;
}
double A(ll i,ll j)
{
	return -2*dp[i]+i*i+i-(-2*dp[j]+j*j+j);
}
double B(ll i,ll j)
{
	return i-j;
}
void cdq(int l,int r)
{
	if( l == r ) 
	{
		dp[a[l].id] = max(dp[a[l].id],a[l].val - (a[l].id - 1)*a[l].id / 2);
		return;
	}
	int mid = (l+r)>>1;
	cdq(l,mid);
	sort(a+l,a+1+mid,cmp1);
	sort(a+1+mid,a+1+r,cmp1);
	ll j = l;
	dp[0] = 0;
	int head = 1,tail = 1;
	q[tail] = 0;
	for (ll i = mid+1; i <= r; i++)
	{
		while( j <= mid && a[j].id < a[i].id )
		{
			while( head < tail && A(a[j].id,q[tail])/B(a[j].id,q[tail]) <= A(q[tail],q[tail-1])/B(q[tail],q[tail-1]) ) tail--;
			q[++tail] = a[j].id;
			j ++;
		}
		while( head < tail && A(q[head+1],q[head])/B(q[head+1],q[head]) <= 2*a[i].id ) head++;
		dp[a[i].id] = max(dp[a[i].id],dp[q[head]] - (a[i].id-q[head]-1)*(a[i].id-q[head])/2 + a[i].val);
	}
	sort(a+1+mid,a+1+r);
	cdq(mid+1,r);
}

int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);
	ll n;
	cin >> n;
	for (int i = 1; i <= n; i++)
	{
		cin >> a[i].val;
		a[i].id = i;
		dp[i] = -1e18;
	}
	sort(a+1,a+1+n); 
	cdq(1,n);
	ll ans = -1e18;
	for (int i = 0; i <= n; i++)
	{
		ans = max(ans,dp[i]-(n-i)*(n-i+1)/2);
	} 
	cout << ans << '\n';
	return 0;
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值