题目描述
小爱是一名教练,她需要从 n 位报名参加运动队的选手中,挑选出一批队员。一名运动员的身体素质可以分为三项:耐力、爆发、技巧,其中第 𝑖i 名选手的耐力、爆发与技巧分别为 xi、yi 与 zi。
小爱希望运动队里的每名队员都能各有长处,如果一个队员的三项素质都不如另一个队员,那就没有出场的机会了。若对于一个运动员 i,存在另一个运动员 j,满足 xi<xj 且 yi<yj 且 zi<zj,则称 i 为不可用的,否则就称 i 是可用的。
请问帮助小爱计算一下,有多少运动员是可用的。
输入格式
第一行:单个正整数 n。
第二行到第 n+1 行:第 i+1 行有三个正整数 xi、yi 与 zi。
输出格式
单个正整数:表示可用运动员的数量。
数据范围
- 对于 30%30% 的数据,1≤𝑛≤2001≤n≤200;
- 对于 60%60% 的数据,1≤𝑛≤50001≤n≤5000;
- 对于 100%100% 的数据,1≤𝑛≤100,0001≤n≤100,000;
- 1≤xi,yi,zi≤n。
样例数据
输入:
5
3 1 1
1 3 1
1 1 3
2 2 2
1 1 1
输出:
4
//前三名运动员各有一项素质是最高的,
//第四名运动员各项均衡也是可用的,第五名运动是不可用的
本题就是一道三维偏序的题,问有几个三元组不严格小于所有其他的三元组。
我们先把所有三元组用 x 从大到小排序,其中对于相等的 x 按照 y 从小到大排序(接下来会说为什么要这样),这样就固定了 x 这个维度。接下来我们维护一个树状数组,存后缀最大值。依次遍历排序后的三元组,将 z 打入树状数组的第 𝑦y 位,然后查询 y+1 的后缀和 a,判断是否大于 z,如果大于 z:
-
易知 z 在这一个三元组之前打入的都满足 x 大于现在的 x,或者 x 等于现在的 x 且 y 小于等于现在的 y,其中存在有一个 y 大于现在的 y(才能纳入查询范围中),z(最大值)大于现在的 z。很容易先排除掉第二种情况,那么剩下的情况就是 x,y,z 均大于现在的x,y,z,可以将现在的三元组淘汰。
-
否则的话,对于所有已经遍历的三元组,大于现在 z 的三元组的 z,y 都小于等于现在的 y,不能完全将其淘汰。对于没有遍历到的三元组,x 都小于等于现在的 x,仍旧不能完全将其淘汰,故可以入选。
也就是说,如果 a≤z,那么就说明找到了一个三元组,计入答案即可。树状数组维护后缀和只需将数组颠倒过来维护前缀和就行了。本题时间复杂度为 O(nlogn)。
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
struct node{
int x,y,z;
bool operator<(const node&r)const{
if(x-r.x)return x>r.x;
return y>r.y;
}
}tp[N];
int n,ans,tr[N];
int ask(int xx){
int ret=n+1;
while(xx){
ret=min(ret,tr[xx]);
xx-=xx&-xx;
}return ret;
}void add(int xx,int rn){
while(xx<=n){
tr[xx]=min(tr[xx],rn);
xx+=xx&-xx;
}
}signed main(){
cin>>n;
for(int i=1;i<=n;i++){
cin>>tp[i].x>>tp[i].y>>tp[i].z;
tp[i].y=n+1-tp[i].y;
tp[i].z=n+1-tp[i].z;
tr[i]=n+1;
}sort(tp+1,tp+n+1);
for(int i=1;i<=n;i++){
int num=ask(tp[i].y-1);
if(num>=tp[i].z){
ans++;
}add(tp[i].y,tp[i].z);
}cout<<ans;
}