三维偏序 CDQ

27 篇文章 0 订阅
10 篇文章 0 订阅

来看这样一道题

(bzoj陌上百合)

描述:
给出 n 个点(x,y,z),请找出最长上升子序列,即对于选择序列中的 i<j,
xi<=xj,yi<=yj,zi<=zj。
输出最长上升子序列的长度和方案数。
输入:
第一行包含一个整数 n
接下来 n 行,每行有 3 个整数 xi,yi,zi
输出:
输出长度和方案数(方案数对2^30取mod)
输入输出样例:
cdq.in cdq.out
3
2 0 0
0 1 1
0 1 0
2 1
5
3 0 0
0 1 0
0 0 1
0 2 2
3 3 3
3 2
数据范围:
x,y,z<=1000000000
对于 20%数据 n<=1000
对于 100%数据 n<=100000


这就是经典的3维偏序.

三维偏序是可以对点排序之后求的


首先看二维偏序:(x,y)

如果我们对x排序,那么就转化成求y的lis


同理,我们也需要一些转化.

按照cdq分治基本思想,一维排序,二维分治,三维树状数组(数据结构)


-----------------------------------------------------------------------------------------------

接下来就以这道题为例,浅谈cdq分治思想

假设我们按第一维的坐标排好序了,我们就需要在x排好序的点对(y,z)中找二维偏序.

因为之前已经排过序(x)了,所以直接再排序是有一点问题的,在这里可以写树套树维护.

树套树的码量有点大,而且不太好写,所以就不谈了。


谈点实际的:

我们已经对x这一维排好序了,那么我们将[l,r]的点对分成两个区间[l,mid] 和 [mid+1,r]

然后依次递归调用,先处理[l,mid]的信息,然后处理[l,mid]对[mid+1,r]的影响,最后处理[mid+1,r]的信息

(为什么要这样?因为这道题是求三维lis,左边的区间会对右边的产生影响.)

继续刚才的分治:如果我们得到了两个区间的信息,就可以对第3维维护信息了。


可能有些不懂,下面讲一下具体的吧:

(下面是计算[l,mid]对[mid+1,r]的影响)

我们将2分的区间分别按y排序(因为第一维之前排过序,可以保证[mid+1,r]的所有x>=[l,mid]的x. 因为是考虑左区间对右区间的影响,所以就不用考虑区间内部信息)

而后第二位也是有序了的。

然后枚举[mid+1,r]每个元素,并且维护左区间的一个指针i,一旦当前枚举到的元素j的y,就把左区间所有i.y<=j.y的信息更新到树状数组里面去(我们保证了y的单调性,所以复杂度大致是 区间长*更新单次的时间)


树状数组用来干什么?我们枚举到右区间一个j,将左区间中所有i.y<=j.y(第一次排序保证了i.x<=j.x)的z在树状数组对应的位置的dp值更新为max(dp[i]).            所以树状数组是用来维护第3维的dp值

对于枚举到的j,更新了所有i.y<=j.y的i的信息之后dp[j]=query([1,j.z])(这一段区间的最大dp值)+1;


然后把所有dp值求max就得到了答案1


那么怎么求方案呢?

我其实也很迷茫,不过参考了各种资料后,发现只需要再维护一个数组保存方案数即可.

在更新dp值的时候,if(dp[j]<getmax(1,j.z).dp)dp[j]=getmax(1,j.z).dp,更新方案数cnt[j]=getmax(1,j.z).cnt;

                              if(dp[j]==getmax(1,j.z).dp)cnt[j]+=getmax(1,j.z).cnt;


就行了!

有不清楚的,再细想一下吧!


AC代码:

<span style="font-size:18px;">#include<cstdio>
#include<algorithm>
#include<queue>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<cmath>
#include<ctime>
#define LL long long
const LL M=1<<30;
using namespace std;
double st,ft;
const int maxn=100000+20;
struct xnode
{
	int x,y,z,id;
}q[maxn],tl[maxn],tr[maxn];
int dp[maxn];
LL c[maxn];
int n;
bool cmpx(xnode a,xnode b)
{
	if(a.x!=b.x)return a.x<b.x;
	if(a.y!=b.y)return a.y<b.y;
	return a.z<b.z;
}
bool cmpy(xnode a,xnode b)
{
	if(a.y!=b.y)return a.y<b.y;
	return a.z<b.z;
}
int det[maxn];
int m;
int cnt;
int find(int x)
{
	return lower_bound(det+1,det+m+1,x)-det;
}
struct Ans
{
	int dp;
	LL cnt;
	Ans()
	{
		dp=cnt=0;
	}
};
Ans f[maxn];
void updata(int i,int x,LL v)
{
	while(i<=m)
	{
		if(f[i].dp<x)
		{
			f[i].dp=x;
			f[i].cnt=v%M;
		}
		else if(f[i].dp==x)
		{
			f[i].cnt+=v;
			f[i].cnt%=M;
		}
		i+=(i&-i);
	}
}
void clear(int i)
{
	while(i<=m)
	{
		f[i].dp=0;
		f[i].cnt=0;
		i+=(i&-i);
	}
}
Ans query(int i)
{
	Ans ans;
	while(i)
	{
		if(ans.dp<f[i].dp)
		{
			ans.dp=f[i].dp;
			ans.cnt=f[i].cnt%M;
		}
		else if(ans.dp==f[i].dp)
		{
			ans.cnt+=f[i].cnt;
			ans.cnt%=M;
		}
		i-=(i&-i);
	}
	return ans;
}
void work(int l,int r)
{
	int mid=(l+r)>>1;
	int t1,t2;
	t1=t2=0;
	for(int i=l;i<=mid;i++)tl[++t1]=q[i];
	for(int i=mid+1;i<=r;i++)tr[++t2]=q[i];
	sort(tl+1,tl+t1+1,cmpy);
	sort(tr+1,tr+t2+1,cmpy);
	for(int i=1,j=1;j<=t2;j++)
	{
		while(tl[i].y<=tr[j].y&&i<=t1)
		{
			updata(tl[i].z,dp[tl[i].id],c[tl[i].id]);
			i++;
		}
		Ans sum=query(tr[j].z);
		sum.dp++;
		if(dp[tr[j].id]<sum.dp)
		{
			dp[tr[j].id]=sum.dp;
			c[tr[j].id]=sum.cnt%M;
		}
		else if(dp[tr[j].id]==sum.dp)
		{
			c[tr[j].id]+=sum.cnt;
			c[tr[j].id]%=M;
		}
	}
	for(int i=l;i<=mid;i++)clear(q[i].z);
}
void cal(int l,int r)
{
	if(l==r)return ;
	int mid=(l+r)>>1;
	cal(l,mid);
	work(l,r);
	cal(mid+1,r);
}
int main()
{
	freopen("cdq.in","r",stdin);
	freopen("cdq.out","w",stdout);
	cnt=0;
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		q[i].id=i;
		dp[i]=1;
		c[i]=1;
	}
	for(int i=1;i<=n;i++)
	{
		scanf("%d%d%d",&q[i].x,&q[i].y,&q[i].z);
		det[++cnt]=q[i].z;
	}
	sort(det+1,det+cnt+1);
	m=unique(det+1,det+cnt+1)-det-1;
	for(int i=1;i<=n;i++)q[i].z=find(q[i].z);	
	sort(q+1,q+n+1,cmpx);
	cal(1,n);
	int ans=0;
	LL sum=0;
	for(int i=1;i<=n;i++)
	{
		if(dp[i]>ans)
		{
			ans=dp[i];
			sum=c[i]%M;
		}
		else if(dp[i]==ans)
		{
			sum+=c[i];
			sum%=M;
		}
	}
	printf("%d %I64d\n",ans,sum);
	return 0;
}</span>


评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值