【洛谷P3810】【模板】三维偏序(陌上花开)

题目

题目链接:https://www.luogu.com.cn/problem/P3810
\(n\) 个元素,第 \(i\) 个元素有 \(a_i,b_i,c_i\) 三个属性,设 \(f(i)\) 表示满足 \(a_j \leq a_i\)\(b_j \leq b_i\)\(c_j \leq c_i\)\(j \ne i\)\(j\) 的数量。
对于 \(d \in [0, n)\),求 \(f(i) = d\) 的数量。

思路

菜鸡\(OIer\)至今不知道\(cdq\)分治除了能应用在偏序上还能应用在哪里\(qwq\),有没有\(dalao\)可以在评论区给出几道不是偏序的\(cdq\)例题啊,谢谢\(qwq\)

一维偏序就是排序,二维偏序就是排序\(+bit\),三维偏序就是排序\(+cdq+bit\)
先将第一维排序,然后第二维采用分治。
假设现在处理到的分治区间是\([l,r]\),设\(mid=\frac{l+r}{2}\),假设\([l,mid]\)\([mid+1,r]\)都已经计算完贡献。那么现在我们需要处理的就是\([l,mid]\)\([mid+1,r]\)的贡献。
将两个区间分别按照第二位排序,因为最开始已经将第一维排过序,所以现在已经能保证\([l,mid]\)里的任意元素的第一维不大于\([mid+1,r]\)里任意元素的第一维。
维护两个指针\(i,j\),分别扫描两个区间。当\(j\)往后扫一位时,\(i\)就不断往后扫直到元素\(i\)的第二位大于元素\(j\)的第二维。扫描同时将\([l,i)\)里所有元素的第三维扔进一个\(bit\)里,当\(i\)的第二维大于\(j\)的第二维时,\(i\)停止往后扫,同时\(j\)的答案加上\(bit.ask(j \texttt{的第三维})\)
这样的话在最开始的排序时保证了\(i\)第一维不超过\(j\)第一维,在扫描的时候保证了\(i\)第二维不超过\(j\)第二维,在查询前缀和时只能查询到第三维不超过\(j\)的第三维的\(i\)的个数。
这样三维偏序就完成了,时间复杂度\(O(n\log^2 n)\)或者说\(O(n\log n\log k)\),笔者不太严谨。

需要注意的是这道题会存在两个元素的三维全都相同,但此时两者是互相能产生贡献的,所以我们将多个完全相同的元素在最开始就合并,每次\(bit.add\)时变成这个元素的个数,同时这个元素的答案一开始就要加上\((\)这个元素个数\(-1)\)

代码

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

const int N=200010;
int n,m,k,ans[N];

struct node
{
	int a,b,c,cnt,num;
}a[N],b[N];

bool cmp1(node x,node y)
{
	if (x.a<y.a) return 1;
	if (x.a>y.a) return 0;
	if (x.b<y.b) return 1;
	if (x.b>y.b) return 0;
	return x.c<y.c;
}

bool cmp2(node x,node y)
{
	return x.b<y.b;
}

struct BIT
{
	int c[N];
	
	void add(int x,int val)
	{
		if (!x) return;
		for (;x<=k;x+=x&-x)
			c[x]+=val;
	}
	
	int ask(int x)
	{
		int ans=0;
		for (;x;x-=x&-x)
			ans+=c[x];
		return ans;
	}
}bit;

void cdq(int l,int r)
{
	if (l==r) return;
	int mid=(l+r)>>1,i=l;
	cdq(l,mid); sort(a+l,a+mid+1,cmp2);
	cdq(mid+1,r); sort(a+mid+1,a+r+1,cmp2);
	for (int j=mid+1;j<=r;j++)
	{
		for (;a[i].b<=a[j].b && i<=mid;i++)
			bit.add(a[i].c,a[i].num);
		a[j].cnt+=bit.ask(a[j].c);
	}
	for (int j=l;j<i;j++)
		bit.add(a[j].c,-a[j].num);
}

int main()
{
	scanf("%d%d",&m,&k);
	for (int i=1;i<=m;i++)
		scanf("%d%d%d",&b[i].a,&b[i].b,&b[i].c);
	sort(b+1,b+1+m,cmp1);
	for (int i=1;i<=m;i++)
	{
		int last=i;
		while (b[i].a==b[i+1].a && b[i].b==b[i+1].b && b[i].c==b[i+1].c) i++;
		a[++n]=b[last]; a[n].num=i-last+1; a[n].cnt=a[n].num-1;
	}
	cdq(1,n);
	for (int i=1;i<=n;i++)
		ans[a[i].cnt]+=a[i].num;
	for (int i=0;i<m;i++)
		printf("%d\n",ans[i]);
	return 0;
}
posted @ 2020-01-21 16:26  stoorz  阅读(170)  评论(0编辑  收藏  举报