中石油训练赛 - Check List(线段树维护偏序问题)

题目大意:给出 n 个点,需要计算出满足下列条件的三元对 ( i , j , k ) 的数量:

  1. x[ i ] < x[ j ] < x[ k ]
  2. y[ k ] > y[ i ] > y[ j ]

题目分析:可以先对 x 进行排序,然后从左到右枚举每一个点去计算其作为三元对中的第一个点、第二个点和第三个点时的贡献

先想一个简单版本的问题,如果将偏序问题转换为:

  1. x[ i ] < x[ j ] < x[ k ]
  2. y[ i ] < y[ j ] < y[ k ]

这样该如何去求解呢?这个模型相对就比较简单了,可以直接用线段树在 y 轴上维护一些变量:

  1. sum1:有多少个点可以作为三元对中的第一个点
  2. sum2:作为三元对中的第二个点,有多少个 二元点对 ,满足 y[ i ] < y[ j ],将这个 点对 的位置记录在位置 y[ j ] 上

更新的话也比较简单,因为所有点已经对 x 轴进行排序了,所以只需要按照如下顺序更新即可,假设当前枚举到的点为 ( x , y ):

  1. 累加一下贡献:区间 [ 1 , y - 1 ] 中的 sum2,因为已经有 sum2 个 二元点对 满足 y[ i ] < y[ j ] 了,且满足 y[ j ] < y,所以这些点对加上当前的点形成的三元对显然满足 y[ i ] < y[ j ] < y
  2. 更新一下当前点作为第二个点时的贡献:
    1. 查询一下区间 [ 1 , y - 1 ] 内有多少个 sum1,记为 val
    2. 第 y 个位置的 sum2 加上 val
    3. 显然 val 个点都小于 y ,与 y 组成的二元点对满足 sum2 的定义
  3. 更新一下当前点作为第一个点时的贡献:第 y 个位置的 sum1 加上 1

时间复杂度是 nlogn 的,下面思考一下类比于上面的方法来解决本题

仍然是用线段树在 y 轴上维护一些变量:

  1. sum1:有多少个点可以作为三元对中的第一个点
  2. sum2:作为三元对中的第二个点,有多少个 二元点对,满足 y[ i ] > y[ j ],将这个 点对 的位置记录在位置 y[ i ] 上

类比于上面的更新,设当前点为 ( x , y ):

  1. 累加一下贡献:区间 [ 1 , y - 1 ] 中的 sum2,因为已经有 sum2 个 二元点对 满足 y[ i ] > y[ j ] 了,且满足 y[ i ] < y,所以这些点对加上当前的点形成的三元对显然满足 y > y[ i ] > y[ j ]
  2. 更新一下当前点作为第二个点时的贡献:
    1. 将区间 [ y + 1 , inf ] 内的所有 sum2 都加上 sum1
    2. 因为我们需要将 y[ i ] > y 的这个二元对保存在 y[ i ] 的位置上,所以对于所有大于 y 的 y[ i ] 来说都需要执行加一的操作,也就是对应着上述的操作(有点绕,可以慢慢想想)
  3. 更新一下当前点作为第一个点时的贡献:第 y 个位置的 sum1 加上 1

需要注意的点是:

  1. 对于 x 轴相同的点需要统一处理
  2. 需要对 y 轴上的点离散化一下建线段树

代码:

#pragma GCC optimize(2)
#pragma GCC optimize("Ofast","inline","-ffast-math")
#pragma GCC target("avx,sse2,sse3,sse4,mmx")
#include<iostream>
#include<cstdio>
#include<string>
#include<ctime>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<stack>
#include<climits>
#include<queue>
#include<map>
#include<set>
#include<sstream>
#include<cassert>
#include<bitset>
using namespace std;
     
typedef long long LL;
     
typedef unsigned long long ull;
     
const int inf=0x3f3f3f3f;
   
const int N=1e5+100;

vector<int>node;

struct Point
{
	int x,y;
	bool operator<(const Point& t)const
	{
		return x<t.x;
	}
}p[N];

struct Node
{
	int l,r;
	LL sum1;//有多少个第一个点
	LL sum2;//有多少个第一个点与第二个点的匹配关系
	LL lazy;//sum2的下传标记 
}tree[N<<2];

void pushup(int k)
{
	tree[k].sum1=tree[k<<1].sum1+tree[k<<1|1].sum1;
	tree[k].sum2=tree[k<<1].sum2+tree[k<<1|1].sum2;
}

void pushdown(int k)
{
	if(tree[k].lazy)
	{
		LL lz=tree[k].lazy;
		tree[k].lazy=0;
		tree[k<<1].sum2+=tree[k<<1].sum1*lz;
		tree[k<<1|1].sum2+=tree[k<<1|1].sum1*lz;
		tree[k<<1].lazy+=lz;
		tree[k<<1|1].lazy+=lz;
	}
}

void build(int k,int l,int r)
{
	tree[k].l=l;
	tree[k].r=r;
	tree[k].sum1=tree[k].sum2=tree[k].lazy=0;
	if(l==r)
		return;
	int mid=l+r>>1;
	build(k<<1,l,mid);
	build(k<<1|1,mid+1,r);
}

void update1(int k,int pos)//更新第一个点的贡献 
{
	if(tree[k].l==tree[k].r)
	{
		tree[k].sum1++;
		return;
	}
	pushdown(k);
	int mid=tree[k].l+tree[k].r>>1;
	if(pos<=mid)
		update1(k<<1,pos);
	else
		update1(k<<1|1,pos);
	pushup(k);
}

void update2(int k,int l,int r)//更新第一个点与第二个点的匹配贡献 
{
	if(tree[k].r<l||tree[k].l>r)
		return;
	if(tree[k].l>=l&&tree[k].r<=r)
	{
		tree[k].sum2+=tree[k].sum1;
		tree[k].lazy++;
		return;
	}
	pushdown(k);
	update2(k<<1,l,r);
	update2(k<<1|1,l,r);
	pushup(k);
}

LL query(int k,int l,int r)//返回第一个点和第二个点的匹配贡献 
{
	if(tree[k].r<l||tree[k].l>r)
		return 0;
	if(tree[k].l>=l&&tree[k].r<=r)
		return tree[k].sum2;
	pushdown(k);
	return query(k<<1,l,r)+query(k<<1|1,l,r);
}

void discreate()
{
	sort(node.begin(),node.end());
	node.erase(unique(node.begin(),node.end()),node.end());
}

int get_id(int x)
{
	return lower_bound(node.begin(),node.end(),x)-node.begin()+1;
}

int main()
{
#ifndef ONLINE_JUDGE
//  freopen("data.in.txt","r",stdin);
//  freopen("data.out.txt","w",stdout);
#endif
//  ios::sync_with_stdio(false);
	int n;
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		scanf("%d%d",&p[i].x,&p[i].y);
		node.push_back(p[i].y);
	}
	sort(p+1,p+1+n);
	discreate();
	build(1,1,node.size());
	LL ans=0;
	for(int i=1,pos;i<=n;i++)
	{
		pos=i;
		while(pos<=n&&p[pos].x==p[i].x)//更新答案(作为第三个点的贡献)
		{
			int y=get_id(p[pos].y);
			ans+=query(1,1,y-1);
			pos++;
		}
		pos=i;
		while(pos<=n&&p[pos].x==p[i].x)//更新第一个点和第二个点的匹配关系 
		{
			int y=get_id(p[pos].y);
			update2(1,y+1,node.size());
			pos++;
		}
		pos=i;
		while(pos<=n&&p[pos].x==p[i].x)//更新第一个点的贡献 
		{
			int y=get_id(p[pos].y);
			update1(1,y);
			pos++;
		}
		i=pos-1;
	}
	printf("%lld\n",ans);






























    return 0;
}

 

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Frozen_Guardian

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值