三位偏序,CDQ分治入门

发布时间 2023-12-29 14:52:11作者: HL_ZZP

(我发现我最近dp没有进展,导致我开始刷水题了。。)
cdp分治,我蓝书又又看不懂了
所以我还是自己去找题目做的
看了看,这个应该才算是真正的入门吧

这里先放上一句我觉得非常重要的话吧

CDQ分治有一个重要的思想——用一个子问题来计算对另一个子问题的贡献。

看到最后我对这句话的理解会又多少吧
二维偏序非常简单,就是排序+树状数组,排序先维护了一维,从第一维上保证了答案的有效性,树状数组以下标统计保证了第二维
逆序对统计就是二位偏序,这是树状数组的入门题和经典运用

那么三位偏序呢?
很明显,三位偏序需要在统计的时候再通过一些方式来保证树状数组中被加入的是合法的,也就是\(a[i]<a[j],b[i]<b[j]\)同时成立,而第三维由树状数组来保证。

我们考虑让第一维的维护方法不变,依旧是以\(a[i]\)为第一关键字进行排序。
然后再此基础上,以\(b[i]\)为第一关键字进行归并排序的过程
考虑这个过程的某一个阶段,此时,以这个区间的中间作为分界线,这个分界线的左边和右边已经分别以\(b[i]\)作为关键字排序完毕了,但是整体还没有以\(b[i]\)为关键字进行全部的排序(只要把左右两边合并一下就是全部排序完毕了),这个时候,因为先前的排序,左边的数字的\(a[i]\)和右边的数字的\(a[i]\)相比,一定是更小的,也就是如果左边的数字和右边的数字的\(b[i]\)也满足条件,那就可以放进树状数组里面进行答案的统计了,这个时候的数组,只有这个分界线左边的数字可能能和右边的数字产生贡献,我们只需要在此基础上找到合法的\(b[i]\)的组合就可以了
大致过程就是这个。。很抽象,我理解了挺久的

我归并排序的写法写挂了
其实没必要归并排序,直接对每一个小区间用sort,复杂度不变

额,有一些细节,就是去重,这个就自己考虑吧

#include<bits/stdc++.h>
#define ll long long
using namespace std;
inline ll read() {
	char c=getchar();ll a=0,b=1;
	for(;c<'0'||c>'9';c=getchar())if(c=='-')b=-1;
	for(;c>='0'&&c<='9';c=getchar())a=a*10+c-48;return a*b;
}
ll n,tot,k,tr[200001],a[200001],b[200001],c[200001],v[200001],q[200001],p[200001],cnt[200001],ans[200001];
inline ll lowbit(ll x)
{
	return x&(-x);
}
bool mycmp(ll x,ll y)
{
    return a[x]<a[y]||(a[x]==a[y]&&(b[x]<b[y]||(b[x]==b[y]&&c[x]<c[y])));
}
bool mycmp2(ll x,ll y)
{
	if(b[x]==b[y])return c[x]<c[y];
	return b[x]<b[y];
}
inline void add(ll i,ll x)
{
	while(i<=k)
	{
		tr[i]+=x;
		i+=lowbit(i);
	}
}
inline ll ask(ll i)
{
	ll ans=0;
	while(i>0)
	{
		ans+=tr[i];
		i-=lowbit(i);
	}
	return ans;
}
void cdq(ll l,ll r)
{
	if(l==r)return ;
	ll mid=(l+r)>>1;
	cdq(l,mid);cdq(mid+1,r);
	sort(p+l,p+mid+1,mycmp2);
	sort(p+mid+1,p+r+1,mycmp2);
	ll i=mid+1,j=l;
	while(i<=r)
	{
		while(b[p[j]]<=b[p[i]]&&j<=mid)
		{
			add(c[p[j]],v[p[j]]);
			j++;
		}
		cnt[p[i]]+=ask(c[p[i]]);
		i++;
	}
	for(ll i=l;i<j;i++)
	{
		add(c[p[i]],-v[p[i]]);
	}
}
int main()
{
//	freopen(".in","r",stdin);
//	freopen(".out","w",stdout);
	n=read();k=read();
	for(ll i=1;i<=n;i++)
	{
		a[i]=read();b[i]=read();c[i]=read();
		p[i]=i;
	}
	sort(p+1,p+1+n,mycmp);ll tot=1;
	for(ll i=2;i<=n;i++)//ºÃ¼ò½àµÄÈ¥ÖØ 
	{
		ll x=p[i],y=p[tot];++v[y];
		if(a[x]!=a[y]||b[x]!=b[y]||c[x]!=c[y])p[++tot]=x;
	}
	v[p[tot]]++;
	cdq(1,tot);
	for(ll i=1;i<=tot;i++)
	{
		ans[cnt[p[i]]+v[p[i]]-1]+=v[p[i]];
	}
	for(ll i=0;i<n;i++)
	{
		cout<<ans[i]<<endl;
	}
	return 0;
}

总算是写过了。。。
这么简单的题目啊

不得不说,跳出舒适圈还是挺不舒服的,写dp多爽啊。。。