[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]);
}
}
[OI] 偏序的更多相关文章
- 牛客OI周赛9-提高组题目记录
牛客OI周赛9-提高组题目记录 昨天晚上做了这一套比赛,觉得题目质量挺高,而且有一些非常有趣而且非常清奇的脑回路在里边,于是记录在此. T1: 扫雷 题目链接 设 \(f_i\) 表示扫到第 \(i\ ...
- [bzoj] 3263 陌上花开 洛谷 P3810 三维偏序|| CDQ分治 && CDQ分治讲解
原题 定义一个点比另一个点大为当且仅当这个点的三个值分别大于等于另一个点的三个值.每比一个点大就为加一等级,求每个等级的点的数量. 显然的三维偏序问题,CDQ的板子题. CDQ分治: CDQ分治是一种 ...
- 正睿OI集训游记
什么嘛....就是去被虐的... 反正就是难受就是了.各种神仙知识点,神仙题目,各式各样的仙人掌..... 但是还是学会了不少东西...... 应该是OI生涯最后一次集训了吧.... 这次的感言还是好 ...
- 洛谷 P2163 [SHOI2007]园丁的烦恼 (离线sort,树状数组,解决三维偏序问题)
P2163 [SHOI2007]园丁的烦恼 题目描述 很久很久以前,在遥远的大陆上有一个美丽的国家.统治着这个美丽国家的国王是一个园艺爱好者,在他的皇家花园里种植着各种奇花异草. 有一天国王漫步在花园 ...
- cdq分治(偏序)
偏序问题: https://www.luogu.org/blog/Owencodeisking/post-xue-xi-bi-ji-cdq-fen-zhi-hu-zheng-ti-er-fen 优质题 ...
- 【教程】CDQ套CDQ——四维偏序问题
前言 上一篇文章已经介绍了简单的CDQ分治,包括经典的二维偏序和三维偏序问题,还有带修改和查询的二维/三维偏序问题.本文讲介绍多重CDQ分治的嵌套,即多维偏序问题. 四维偏序问题 给定N( ...
- 再见,OI
你好,NOIP 2015年9月1日 正式成为了福建省莆田一中的一名高一成员 后来学校搞了选修 大家都很激动 因为自己的兴趣和特长能够得到发挥了(或者说能逃课或者看好多电影) 发现选修提供的选项中有好几 ...
- 告别我的OI生涯
本文章写于2008年12月15日. 随着2008noip的结束,我也结束了我的OI生涯. 信息竞赛也许是从小到大让我最最努力的一件事.我记得参加2006noip初赛前,每天中午为了上信息课都吃不上中午 ...
- c++模板函数实例化的偏序机制
一:废话 今天在stackoverflow上看到一个关于c++模板specialization的问题: http://stackoverflow.com/questions/18283851/temp ...
- 收集一些关于OI/ACM的奇怪的东西……
一.代码: 1.求逆元(原理貌似就是拓展欧几里得,要求MOD是素数): int inv(int a) { if(a == 1) return 1; return ((MOD - MOD / a) * ...
随机推荐
- [rCore学习笔记 016]实现应用程序
写在前面 本随笔是非常菜的菜鸡写的.如有问题请及时提出. 可以联系:1160712160@qq.com GitHhub:https://github.com/WindDevil (目前啥也没有 设计方 ...
- 字符—字符与整数的关系&&常用的库函数_C
// Code file created by C Code Develop #include "ccd.h" #include "stdio.h" #incl ...
- app专项测试:测试内容
app专项测试:测试内容 除了app的UI功能测试,平时听说比较多的就是app专项测试了, app专项测试主要包含以下内容: 1,流量测试 :app静态测试(耗时.流量.内存.图片大小) 2,弱网测试 ...
- Known框架实战演练——进销存财务管理
本文介绍如何实现进销存管理系统的财务对账模块,财务对账模块包括供应商对账和客户对账2个菜单页面.供应商和客户对账字段相同,因此可共用一个页面组件类. 项目代码:JxcLite 开源地址: https: ...
- 压力测试工具httperf使用方法
目录 压力测试工具httperf使用方法 通过tar zxvf解压httperf-0.9.0.tar.gz 进入目录 安装c++编译环境 开始编译 进入编译后的bin目录 开始测试 压力测试工具htt ...
- 【Vue】 签名组件
一.需求背景: 检查业务,检查完成后,执行人需要签字证明检查完成 二.实现效果: 三.技术实现 通过canvas转换成blob对象,可以上传到文件服务,或者是下载另存为到本地磁盘 注意重点,canva ...
- 【Java】在树结构中给节点追加数据
一.功能需求 有个树状组件,展示区域层级,每个区域节点需要展示该地区下的统计信息 从来没做过,给我整不会了属实是 二.功能分析 原型有功能和老系统代码,查看源码后发现的结构框架 1.树组件是自己用ul ...
- 【Vue】el-select 数据过多替代方案
一.需求问题: 一开始就考虑使用简单el-select选取数据,但是后面数据量增多, 超过一千条开始,组件会很卡不好用,第二个是接口也慢了 数据量多的话是有一个filterable做支持了,可以输入关 ...
- 【MySQL】下发功能SQL
SQL参考文章: https://www.jb51.net/article/15627.htm 下发,就是从别的表中同步数据到此表中,也可能是来自不同库的表,或者不同实例的表 下发的逻辑要求:如果没有 ...
- 使用 Alba 对 AspnetCore项目进行测试
前言 在AspnetCore生态系统中,我们测试项目一般使用Microsoft.AspNetCore.TestHost的TestServer 到.NET6后提供的Microsoft.AspNetCor ...