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;
}