题目大意:给出 n 个点,需要计算出满足下列条件的三元对 ( i , j , k ) 的数量:
- x[ i ] < x[ j ] < x[ k ]
- y[ k ] > y[ i ] > y[ j ]
题目分析:可以先对 x 进行排序,然后从左到右枚举每一个点去计算其作为三元对中的第一个点、第二个点和第三个点时的贡献
先想一个简单版本的问题,如果将偏序问题转换为:
- x[ i ] < x[ j ] < x[ k ]
- y[ i ] < y[ j ] < y[ k ]
这样该如何去求解呢?这个模型相对就比较简单了,可以直接用线段树在 y 轴上维护一些变量:
- sum1:有多少个点可以作为三元对中的第一个点
- sum2:作为三元对中的第二个点,有多少个 二元点对 ,满足 y[ i ] < y[ j ],将这个 点对 的位置记录在位置 y[ j ] 上
更新的话也比较简单,因为所有点已经对 x 轴进行排序了,所以只需要按照如下顺序更新即可,假设当前枚举到的点为 ( x , y ):
- 累加一下贡献:区间 [ 1 , y - 1 ] 中的 sum2,因为已经有 sum2 个 二元点对 满足 y[ i ] < y[ j ] 了,且满足 y[ j ] < y,所以这些点对加上当前的点形成的三元对显然满足 y[ i ] < y[ j ] < y
- 更新一下当前点作为第二个点时的贡献:
- 查询一下区间 [ 1 , y - 1 ] 内有多少个 sum1,记为 val
- 第 y 个位置的 sum2 加上 val
- 显然 val 个点都小于 y ,与 y 组成的二元点对满足 sum2 的定义
- 更新一下当前点作为第一个点时的贡献:第 y 个位置的 sum1 加上 1
时间复杂度是 nlogn 的,下面思考一下类比于上面的方法来解决本题
仍然是用线段树在 y 轴上维护一些变量:
- sum1:有多少个点可以作为三元对中的第一个点
- sum2:作为三元对中的第二个点,有多少个 二元点对,满足 y[ i ] > y[ j ],将这个 点对 的位置记录在位置 y[ i ] 上
类比于上面的更新,设当前点为 ( x , y ):
- 累加一下贡献:区间 [ 1 , y - 1 ] 中的 sum2,因为已经有 sum2 个 二元点对 满足 y[ i ] > y[ j ] 了,且满足 y[ i ] < y,所以这些点对加上当前的点形成的三元对显然满足 y > y[ i ] > y[ j ]
- 更新一下当前点作为第二个点时的贡献:
- 将区间 [ y + 1 , inf ] 内的所有 sum2 都加上 sum1
- 因为我们需要将 y[ i ] > y 的这个二元对保存在 y[ i ] 的位置上,所以对于所有大于 y 的 y[ i ] 来说都需要执行加一的操作,也就是对应着上述的操作(有点绕,可以慢慢想想)
- 更新一下当前点作为第一个点时的贡献:第 y 个位置的 sum1 加上 1
需要注意的点是:
- 对于 x 轴相同的点需要统一处理
- 需要对 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;
}