[OI] 偏序
\(n\) 维偏序即给出若干个点对 \((a_{i},b_{i},\cdots,n_{i})\),对每个 \(i\) 求出满足 \(a_{j}\gt a_{i},b_{j}\gt b_{i}\cdots,n_{j}\gt n_{i}\) 的 \(j\) 的个数
一维偏序
直接用权值线段树或者树状数组. 或者你直接离散化开桶前缀和.
二维偏序
考虑到先对全部点对按 \(a_{i}\) 排序,这样,任何点对只能被它前面的点产生贡献.
a 1 1 1 2 3 4 4 5
b 1 2 3 3 4 2 4 1
可以发现,\(i\) 处的答案可以转化为:其前方的点中满足一维偏序的点数.
所以我们考虑对点对从左到右扫一遍,每次向维护的动态树状数组中加入一个新节点,统计前缀和即可.
三维偏序
CDQ+数据结构 解法
按二维偏序的套路,先对 \(a_{i}\) 排序
然后我们考虑这样分解问题:向下对 \(b\) 进行归并排序,每次只统计 \(i\in[l,mid],j\in[mid+1,r]\) 的答案. 可以发现,对于区间内部的答案,总会有递归下去的子区间来求解,对于区间之间的答案,当前这一步将会求解出来,因此我们恰好统计了所有答案,这就是 CDQ 分治.
为什么我们在进行统计答案的时候需要对 \(b\) 排序?假设现在两个子区间内的 \(b\) 都是有序的(\(a\) 是完全有序的,我们不管它),那么在对 \(b\) 归并排序的过程中,会发现,只有 \(i\) 能对 \(j\) 产生贡献(因为 \(a\) 数组的限制),并且只有 \(i\) 数对的 \(b\) 比 \(j\) 数对的 \(b\) 更小的时候,\(i\) 才可能对 \(j\) 有贡献,表现在归并排序里就是只有比 \(j\) 先选的 \(i\) 才能对 \(j\) 有贡献. 基于这一点,我们才能用树状数组+CDQ分治来解决偏序问题.
#include<bits/stdc++.h>
using namespace std;
#define p &
int n,k;
struct node{
int a,b,c;
int cnt,res;
bool operator !=(const node &A)const{
return a!=A.a or b!=A.b or c!=A.c;
}
};
node e[100001];
node ue[100001];
int m,t;
int res[100001];
struct bit{
int a[200001];
function<int(int)> lowbit=[](int x){return x&-x;};
inline void add(int pos,int val){
while(pos<=k){
a[pos]+=val;
pos+=lowbit(pos);
}
};
inline int ask(int pos){//sum[pos]
int res=0;
while(pos){
res+=a[pos];
pos-=lowbit(pos);
}
return res;
};
}BIT;
bool cmp1(node a,node b){
if(a.a!=b.a) return a.a<b.a;
if(a.b!=b.b) return a.b<b.b;
return a.c<b.c;
}
bool cmp2(node a,node b){
if(a.b!=b.b) return a.b<b.b;
return a.c<b.c;
}
void cdq(int l,int r){
if(l==r) return;//i!=j 单个区间无法产生贡献
int mid=(l+r)/2;
cdq(l,mid);//递归求解子区间
cdq(mid+1,r);
sort(ue+l,ue+mid+1,cmp2);//按 b 排序,方便归并
sort(ue+mid+1,ue+r+1,cmp2);
int i=l,j=mid+1;//归并排序
//注意这里并不会真的排序,因为父区间会有 sort 的
while(j<=r){
while(i<=mid and ue[i].b<=ue[j].b){
//当前 i 更小,将会对 j 与 j 后方的位置产生贡献
BIT.add(ue[i].c,ue[i].cnt);
i++;
}
//后面的 i 值不再能够产生贡献了,此时暂时统计答案
ue[j].res+=BIT.ask(ue[j].c);
j++;
}
for(int k=l;k<=i-1;++k){
BIT.add(ue[k].c,-ue[k].cnt);
//清空 BIT,memset 也行就是有点慢
}
}
int main(){
scanf("%d %d",p n,p k);
for(int i=1;i<=n;++i){
scanf("%d %d %d",p e[i].a,p e[i].b,p e[i].c);
}
sort(e+1,e+n+1,cmp1);
for(int i=1;i<=n;++i){
t++;
if(e[i]!=e[i+1]){
//去重,防止相同的元素统计不到而漏答案
ue[++m]={e[i].a,e[i].b,e[i].c,t};
t=0;
}
}
cdq(1,m);
for(int i=1;i<=m;++i){
//去重在这里用到了
res[ue[i].res+ue[i].cnt-1]+=ue[i].cnt;
}
for(int i=0;i<=n-1;++i){
printf("%d\n",res[i]);
}
}
数据结构+数据结构
众所周知,三维偏序可以用树套树来解决.
我并不是非常喜欢写树套树,因此就写了一个树套树
我说怎么没人线段树套线段树,原来是会 TLE 啊,哈哈哈😅
考虑到这个题排完序只需要对每一维维护动态前缀和即可,可以想到树套树.
UPD:糙拟妈啥比树套树爱谁调谁调😠😠😠😠😠😠😠😠😠😠😠😠
10pts
#include<bits/stdc++.h>
using namespace std;
int n,k;
struct node{
int val;
node *tol,*tor;
node(){val=0;tol=tor=NULL;}
};
class Stree{
public:
node *root;
void change(node *&now,int pos,int val,int l,int r){
if(!now) now=new node();
if(l==r){
now->val+=val;
return;
}
int mid=(l+r)/2;
if(pos<=mid) change(now->tol,pos,val,l,mid);
else change(now->tor,pos,val,mid+1,r);
now->val=(now->tol?now->tol->val:0)+(now->tor?now->tor->val:0);
}
int ask(node *now,int l,int r,int nl,int nr){
if(!now) return 0;
if(l==nl and r==nr) return now->val;
int mid=(nl+nr)/2;
if(r<=mid) return ask(now->tol,l,r,nl,mid);
else if(l>mid) return ask(now->tor,l,r,mid+1,nr);
return ask(now->tol,l,mid,nl,mid)|ask(now->tor,mid+1,r,mid+1,nr);
}
};
//struct node{
// int val;
// int tol,tor;
//};
//vector<node>t;
//int newnode(){
// t.push_back({0,0,0});
// return t.size()-1;
//}
//class Stree{
// public:
// int root=0;
// void change(int &now,int pos,int val,int l,int r){
// if(!now) now=newnode();
// if(l==r){
// t[now].val+=val;
// return;
// }
// int mid=(l+r)/2;
// if(pos<=mid) change(t[now].tol,pos,val,l,mid);
// else change(t[now].tor,pos,val,mid+1,r);
// t[now].val=t[t[now].tol].val+t[t[now].tor].val;
// }
// int ask(int now,int l,int r,int nl,int nr){
// if(!now) return 0;
// if(l==nl and r==nr) return t[now].val;
// int mid=(nl+nr)/2;
// if(r<=mid) return ask(t[now].tol,l,r,nl,mid);
// else if(l>mid) return ask(t[now].tor,l,r,mid+1,nr);
// return ask(t[now].tol,l,mid,nl,mid)+ask(t[now].tor,mid+1,r,mid+1,nr);
// }
//};
class BIT{
public:
Stree a[200001];
inline int lowbit(int x){return x&-x;}
inline void add(int posx,int posy,int val){
for(int i=posx;i<=k;i+=lowbit(i)){
a[i].change(a[i].root,posy,val,1,k);
}
}
inline int ask(int x,int y){
int ans=0;
for(int i=x;i;i-=lowbit(i)){
ans+=a[i].ask(a[i].root,1,y,1,k);
}
return ans;
}
}bit;
struct un{
int x,y,z;
bool operator <(const un &A)const{
if(x!=A.x) return x<A.x;
if(y!=A.y) return y<A.y;
return z<A.z;
}
bool operator ==(const un &A)const{
return x==A.x and y==A.y and z==A.z;
}
}d[100001];
int ans[100001];
int main(){
scanf("%d %d",&n,&k);
for(int i=1;i<=n;++i){
scanf("%d %d %d",&d[i].x,&d[i].y,&d[i].z);
}
sort(d+1,d+n+1);
int t=1;
for(int i=1;i<=n;++i){
if(d[i+1]==d[i]){
t++;
continue;
}
bit.add(d[i].y,d[i].z,t);
int res=bit.ask(d[i].y,d[i].z);
ans[res]+=t;
t=1;
}
for(int i=1;i<=n;++i){
printf("%d\n",ans[i]);
}
}
UPD: 码的调出来了. 哪个啥比把 |
设计在 +
旁边了😠
在 ask()
函数最后一行把 +
写成 |
了
#include<bits/stdc++.h>
using namespace std;
int n,k;
struct node{
int val;
node *tol,*tor;
node(){val=0;tol=tor=NULL;}
};
class Stree{
public:
node *root;
void change(node *&now,int pos,int val,int l,int r){
if(!now) now=new node();
if(l==r){
now->val+=val;
return;
}
int mid=(l+r)/2;
if(pos<=mid) change(now->tol,pos,val,l,mid);
else change(now->tor,pos,val,mid+1,r);
now->val=(now->tol?now->tol->val:0)+(now->tor?now->tor->val:0);
}
int ask(node *now,int l,int r,int nl,int nr){
if(!now) return 0;
if(l==nl and r==nr) return now->val;
int mid=(nl+nr)/2;
if(r<=mid) return ask(now->tol,l,r,nl,mid);
else if(l>mid) return ask(now->tor,l,r,mid+1,nr);
return ask(now->tol,l,mid,nl,mid)+ask(now->tor,mid+1,r,mid+1,nr);
}
};
//struct node{
// int val;
// int tol,tor;
//};
//vector<node>t;
//int newnode(){
// t.push_back({0,0,0});
// return t.size()-1;
//}
//class Stree{
// public:
// int root=0;
// void change(int &now,int pos,int val,int l,int r){
// if(!now) now=newnode();
// if(l==r){
// t[now].val+=val;
// return;
// }
// int mid=(l+r)/2;
// if(pos<=mid) change(t[now].tol,pos,val,l,mid);
// else change(t[now].tor,pos,val,mid+1,r);
// t[now].val=t[t[now].tol].val+t[t[now].tor].val;
// }
// int ask(int now,int l,int r,int nl,int nr){
// if(!now) return 0;
// if(l==nl and r==nr) return t[now].val;
// int mid=(nl+nr)/2;
// if(r<=mid) return ask(t[now].tol,l,r,nl,mid);
// else if(l>mid) return ask(t[now].tor,l,r,mid+1,nr);
// return ask(t[now].tol,l,mid,nl,mid)+ask(t[now].tor,mid+1,r,mid+1,nr);
// }
//};
class BIT{
public:
Stree a[200001];
inline int lowbit(int x){return x&-x;}
inline void add(int posx,int posy,int val){
for(int i=posx;i<=k;i+=lowbit(i)){
a[i].change(a[i].root,posy,val,1,k);
}
}
inline int ask(int x,int y){
int ans=0;
for(int i=x;i;i-=lowbit(i)){
ans+=a[i].ask(a[i].root,1,y,1,k);
}
return ans;
}
}bit;
struct un{
int x,y,z;
bool operator <(const un &A)const{
if(x!=A.x) return x<A.x;
if(y!=A.y) return y<A.y;
return z<A.z;
}
bool operator ==(const un &A)const{
return x==A.x and y==A.y and z==A.z;
}
}d[200001];
int ans[200001];
int main(){
scanf("%d %d",&n,&k);
for(int i=1;i<=n;++i){
scanf("%d %d %d",&d[i].x,&d[i].y,&d[i].z);
}
sort(d+1,d+n+1);
int t=1;
for(int i=1;i<=n;++i){
if(d[i+1]==d[i]){
t++;
continue;
}
bit.add(d[i].y,d[i].z,t);
int res=bit.ask(d[i].y,d[i].z);
ans[res]+=t;
t=1;
}
for(int i=1;i<=n;++i){
printf("%d\n",ans[i]);
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· 使用C#创建一个MCP客户端
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· Windows编程----内核对象竟然如此简单?