bzoj 3262: 陌上花开(cdq分治)

3262: 陌上花开

Time Limit: 20 Sec   Memory Limit: 256 MB
Submit: 1431   Solved: 644
[ Submit][ Status][ Discuss]

Description

有n朵花,每朵花有三个属性:花形(s)、颜色(c)、气味(m),又三个整数表示。现要对每朵花评级,一朵花的级别是它拥有的美丽能超过的花的数量。定义一朵花A比另一朵花B要美丽,当且仅当Sa>=Sb,Ca>=Cb,Ma>=Mb。显然,两朵花可能有同样的属性。需要统计出评出每个等级的花的数量。

Input

第一行为N,K (1 <= N <= 100,000, 1 <= K <= 200,000 ), 分别表示花的数量和最大属性值。
以下N行,每行三个整数si, ci, mi (1 <= si, ci, mi <= K),表示第i朵花的属性

Output

包含N行,分别表示评级为0...N-1的每级花的数量。

Sample Input

10 3
3 3 3
2 3 3
2 3 1
3 1 1
3 1 2
1 3 1
1 1 2
1 2 2
1 3 2
1 2 1

Sample Output

3
1
3
0
1
0
1
0
0
1

HINT

1 <= N <= 100,000, 1 <= K <= 200,000

Source

[ Submit][ Status][ Discuss]

题解:cdq分治。

这道题我刚开始是想能不能把三个值搞到一块,然后每次判断只需要对比这个值即可,但是想了很久都没想出来。

那要怎么做呢?我们按照s权值从小到大排序,然后二分s权值区间[l,r],找到权值在[l,r]的区间[x,y],在二分一个中间权值mid,利用二分查找找到值为mid的最后一个数的位置,因为a是从小到大有序的所以只可能是[x,posmid]对后面[posmid+1,y]产生贡献且保证a一定满足条件,所以我们把前面的区间中的数标记,然后按照[x,y]中的数按照c权值从小到大排序,然后按照b的顺序如果有标记就向权值线段树的a[i].m的位置加数,如果没有标记就统计权值线段树中[1,a[i].m]的答案然后添加到ans[a[i].num]。注意那些b,c都相等的花,要放到一起考虑,要一起都加入后再计算。

细节很多,尤其是二分查找!!

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<cmath>
#define N 200003
using namespace std;
struct data
{
	int a,b,c,num;
	int pd;
}a[N];
int n,m,tr[N*4],pd[N*4],minn,maxn,num[N];
int ans[N];
void clear(int now)
{
	tr[now]=0;
	pd[now]=1;
}
int cmp(data a,data b)
{
	return a.a<b.a;
}
int cmp1(data a,data b)
{
	return a.b<b.b||a.b==b.b&&a.c<b.c;
}
void pushdown(int now)
{
	if (pd[now])
    {
    	pd[now]=0;
    	clear(now<<1); clear(now<<1|1);
	}
}
void update(int now)
{
	tr[now]=tr[now<<1]+tr[now<<1|1];
}
void pointchange(int now,int l,int r,int x)
{
	if (l==r)
	 {
	 	tr[now]++;
	 	return;
	 }
	pushdown(now);
	int mid=(l+r)/2;
	if (x<=mid) pointchange(now<<1,l,mid,x);
	else pointchange(now<<1|1,mid+1,r,x);
	update(now);
}
int qjsum(int now,int l,int r,int ll,int rr)
{
	if (ll<=l&&r<=rr)
	 return tr[now];
	pushdown(now);
	int mid=(l+r)/2;
	int ans=0;
	if (ll<=mid)   ans+=qjsum(now<<1,l,mid,ll,rr);
	if (rr>mid) ans+=qjsum(now<<1|1,mid+1,r,ll,rr);
	return ans;
}
int find(int x,int l,int r)
{
	int ans=r;
	int nowr=r,nowl=l;
	if (a[l].a>x)  return l;
	if (a[r].a<x)  return r+1;
	while (l<=r)
	{
		int mid=(l+r)/2;
		if (a[mid].a>=x) ans=min(ans,mid),r=mid-1;
		else  l=mid+1;  
	}
	return ans;
}
void divide(int l,int r,int x,int y)
{
	bool f=true;
	for (int i=x+1;i<=y;i++)
	 if (a[i].a!=a[x].a)
	  {
	  	f=false;
	  	break;
	  }
	if (l>r||x>y) return;
	if (l==r||x==y||f)
	{
		sort(a+x,a+y+1,cmp1);
		clear(1);
		for (int i=x;i<=y;i++)
		 if (a[i].c!=a[i+1].c||i==y||a[i].b!=a[i+1].b)
		  {
		  	pointchange(1,1,m,a[i].c);
		  	int j=i;
		  	while (a[j].c==a[i].c&&a[j].b==a[i].b&&j>=x)
		  	 {
		  	 	int t=qjsum(1,1,m,1,a[j].c);  
		  	 	ans[a[j].num]+=t;
		  	 	j--;
			   }
		  }
		 else pointchange(1,1,m,a[i].c);
		return ;
	}
	int mid=(l+r)/2;
	int posf=x;
	int pose=y;
	int posm=find(mid+1,x,y)-1; 
	clear(1);
	for (int i=posf;i<=posm;i++)  a[i].pd=1;
	for (int i=posm+1;i<=pose;i++)  a[i].pd=0;
	sort(a+posf,a+pose+1,cmp1);
	for (int i=posf;i<=pose;i++)
	 if (a[i].c!=a[i+1].c||a[i].b!=a[i+1].b||i==pose)
		  {
		  	if (a[i].pd==1)   pointchange(1,1,m,a[i].c);
		  	int j=i;
		  	while (a[j].c==a[i].c&&a[j].b==a[i].b&&j>=x)
		  	 {
		  	 	if (a[j].pd==0) 
				   {
					int t=qjsum(1,1,m,1,a[j].c);  
				    ans[a[j].num]+=t;
				   }
		  	 	j--;
			 }
		  }
		 else 
		  if (a[i].pd==1)  pointchange(1,1,m,a[i].c);
	sort(a+posf,a+pose+1,cmp);
	divide(l,mid,posf,posm);
	divide(mid+1,r,posm+1,pose);
}
int main()
{
	freopen("a.in","r",stdin);
	freopen("my.out","w",stdout);
	scanf("%d%d",&n,&m);
	minn=N,maxn=0;
	for (int i=1;i<=n;i++)
	 scanf("%d%d%d",&a[i].a,&a[i].b,&a[i].c),a[i].num=i,
	 minn=min(minn,a[i].a),maxn=max(maxn,a[i].a);
	sort(a+1,a+n+1,cmp);
	divide(1,maxn,1,n);
	for (int i=1;i<=n;i++)
	  num[ans[i]]++;
	for (int i=1;i<=n;i++)
	 printf("%d\n",num[i]);
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值