主席树,喵~

稍微总结一下主席树吧

Too Difficult!搞了一天搞出一大堆怎么令人悲伤的辣鸡代码。总之先总结一下吧,以后碰到这种问题直接拿去毒害队友好了。

UPD 5/24 苟狗是沙比


一个节点记录三个信息:lson,rson,sum

pid表示节点个数。

build

void build(int &k,int l,int r){k=++pid;if(l==r) return;int mid=(l+r)>>1;build(lson[k],l,mid);build(rson[k],mid+1,r);
}

change

void change(int old,int &k,int l,int r,int pos,int x){k=++pid;lson[k]=lson[old],rson[k]=rson[old],sum[k]=sum[old]+x;if(l==r) return;int mid=(l+r)>>1;if(pos<=mid) change(lson[old],lson[k],l,mid,pos,x);else change(rson[old],rson[k],mid+1,r,pos,x);
}

Lv.1 最基本的操作

  • 区间k大值
int query(int old_k,int new_k,int l,int r,int x){if(l==r) return sum[new_k]-sum[old_k]>0?l:-1;int mid=(l+r)>>1;int cntLeft = sum[lson[new_k]]-sum[lson[old_k]];if (cntLeft<x) {return query(rson[old_k],rson[new_k],mid+1,r,x-cntLeft);} else {return query(lson[old_k],lson[new_k],l,mid,x);}
}
  • 区间内有多少个数字小于等于x
int query(int new_k,int old_k,int l,int r,int x) { // cnt <= xif(x<l) return 0; // 这个地方比较喜。小心点。if(l==r) return sum[new_k]-sum[old_k];int mid=(l+r)>>1;if(mid<x) return sum[lson[new_k]]-sum[lson[old_k]]+query(rson[new_k],rson[old_k],mid+1,r,x);else return query(lson[new_k],lson[old_k],l,mid,x);
}
  • 查询区间<=x的最大数字:上两条的组合技。

两道入门题:POJ2104,HDU4417

主席树相当于对每一个前缀都维护一个线段树,然后发现相邻两棵线段树长得好像哎!所以我们可以动态开点啦!

解决问题的时候,我们通常会对每一个前缀,维护一个权值线段树。每个值域存要维护的信息。


既然是维护每一个前缀,所以,我们不仅能拿主席树来施展线性结构,还能施展树状结构!比如说我们可以查询树上两点间路径点权的k小值。

Lv.2 树上路径上点权k小值

栗子:SPOJ-COT

线性结构上

iterval(l,r)=T(r)-T(l-1)

树状结构上

path(u,v) = T(u)+T(v)-T(lca)-T(Parent of lca)


Lv.2 矩形内有多少个点

给出很多个点。Q组询问,每组询问查询一个矩形内有几个点。

按横坐标排序,把纵坐标放到主席树上,然后就相当于区间内有多少个数字小于等于x啦!

栗子:CF853C

把细节考虑好!还是很友好的。

#include <iostream>
#include <algorithm>
using namespace std;
const int N=6000000+10;
#define f(x) (1LL*x*(x-1)/2)
typedef long long LL;
int lson[N],rson[N],sum[N],root[N],pid;
int n,q,p[N];
void build(int &k,int l,int r){k=++pid;if(l==r) return;int mid=(l+r)>>1;build(lson[k],l,mid);build(rson[k],mid+1,r);
}
void change(int old,int &k,int l,int r,int pos,int x) {k=++pid;ilson[k]=lson[old],rson[k]=rson[old],sum[k]=sum[old]+x;if(l==r) return;int mid=(l+r)>>1;if(pos<=mid) change(lson[k],lson[k],l,mid,pos,x);else change(rson[k],rson[k],mid+1,r,pos,x);
}
int query(int new_k,int old_k,int l,int r,int x) { // cnt <= xif(x<l) return 0;if(l==r) return sum[new_k]-sum[old_k];int mid=(l+r)>>1;if(mid<x) return sum[lson[new_k]]-sum[lson[old_k]]+query(rson[new_k],rson[old_k],mid+1,r,x);else return query(lson[new_k],lson[old_k],l,mid,x);
}
int count(int x1,int x2,int y1,int y2) { // if(x1>x2||y1>y2) return 0;int cnt1 = query(root[x2],root[x1-1],1,n,y1-1);int cnt2 = query(root[x2],root[x1-1],1,n,y2);return cnt2-cnt1;
} 
int main(){scanf("%d%d",&n,&q);for(int i=1;i<=n;i++) {scanf("%d",&p[i]);}build(root[0],1,n);for(int i=1;i<=n;i++) {change(root[i-1],root[i],1,n,p[i],1);}for(int i=1;i<=q;i++){int l,d,r,u;scanf("%d%d%d%d",&l,&d,&r,&u);int LU = count(1,l-1,u+1,n);int LD = count(1,l-1,1,d-1);int RU = count(r+1,n,u+1,n);int RD = count(r+1,n,1,d-1);int L = l-1; int U = n-u; int R = n-r; int D = d-1;LL A = f(L)+f(R)+f(U)+f(D);LL B = f(LU)+f(LD)+f(RU)+f(RD);LL ret = 1LL*n*(n-1)/2-(A-B);printf("%lld\n", ret);}
}

Lv.2 区间内出现数字的个数

权值线段树直接投降了,不过我们可以在某个元素上一次出现的位置insert -1,在当前出现的位置insert 1

种树之前想清楚该维护什么啊!

栗子: HDU5919

题解:因为是统计区间内,每个数字第一次出现的位置。

所以我们可以倒着做。从后往前遍历,遇到一个数字,在这个数字上一次出现的位置加上-1,当前位置加上1.

在从后往前遍历的同时,我们对于每一个后缀建一棵线段树。维护后缀中,每个元素第一次出现的位置。

对于每组询问,先求出区间内有多少种不同的数字,然后查询第(cnt+1)/2大即可。

#include <iostream>
#include <map>
using namespace std;
const int N = 10000000+10;
int lson[N],rson[N],root[N],sum[N],pid;
int T,cas;void build(int &k,int l,int r) {k=++pid;if(l==r) return;int mid=(l+r)>>1;build(lson[k],l,mid);build(rson[k],mid+1,r);
}
void update(int old,int &k,int l,int r,int pos,int x) {k=++pid; sum[k] = 0;lson[k]=lson[old], rson[k]=rson[old], sum[k]=sum[old]+x;if(l==r) return;int mid=(l+r)>>1;if (pos<=mid) update(lson[old],lson[k],l,mid,pos,x);elseupdate(rson[old],rson[k],mid+1,r,pos,x);
}
int query_x_th(int k,int l,int r,int x) {if (l == r) return l;int mid = (l+r)>>1;if (sum[lson[k]] < x) {return query_x_th(rson[k],mid+1,r,x-sum[lson[k]]);} else {return query_x_th(lson[k],l,mid,x);}
}
int count(int k,int l,int r,int L,int R) {if(L<=l&&r<=R) {return sum[k];}int mid = (l+r)>>1;int ans = 0;if (L<=mid) ans += count(lson[k],l,mid,L,R);if (R >mid) ans += count(rson[k],mid+1,r,L,R);return ans;
}int n, m, a[N];
map<int,int> las;
void init() {las.clear();pid = 0;
}
int main() {scanf("%d",&T);while (T --) {init();scanf("%d %d",&n,&m);for(int i=1;i<=n;i++) {scanf("%d", &a[i]); }build(root[n+1],1,n);for(int i=n;i>=1;i--) {update(root[i+1],root[i],1,n,i,1);if ( las.find(a[i]) != las.end() )update(root[i],root[i],1,n,las[a[i]], -1);las[a[i]] = i;}printf("Case #%d:", ++cas);int ans=0;for(int i=1;i<=m;i++) {int l, r;scanf("%d %d", &l, &r);int nl = min((l+ans)%n+1, (r+ans)%n+1);int nr = max((l+ans)%n+1, (r+ans)%n+1);int tot = count(root[nl],1,n,nl,nr);ans = query_x_th(root[nl],1,n,(tot+1)/2);printf(" %d", ans);}printf("\n");}
}

Lv.3 主席树的区间更新

一种不用下传懒惰标记的姿势:对于区间查询,从上往下走的时候,对懒惰标记进行累加。

栗子:HDU4348

#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
typedef long long LL;
const int N=6000000+10;
int lson[N],rson[N],root[N],pid;
LL sum[N],lazy[N];
int n,q,a[N];void build(int &k,int l,int r){k=++pid; lazy[k] = 0; sum[k] = 0;if(l==r) {sum[k] = a[l];lson[k] = rson[k] = 0;return;}int mid=(l+r)>>1;build(lson[k],l,mid);build(rson[k],mid+1,r);sum[k] = sum[lson[k]] + sum[rson[k]];
}
void update(int old,int &k,int l,int r,int L,int R,int x){k=++pid; lazy[k] = 0; sum[k] = 0;lazy[k]=lazy[old]; sum[k] = sum[old];lson[k]=lson[old]; rson[k]=rson[old];if(L<=l&&r<=R) {lazy[k] = lazy[old] + x;sum[k] = sum[old] + 1LL*(r-l+1)*x;return;}int mid=(l+r)>>1;if (L<=mid)update(lson[k],lson[k],l,mid,L,R,x);if (R >mid)update(rson[k],rson[k],mid+1,r,L,R,x);sum[k] = sum[lson[k]] + sum[rson[k]] + 1LL*lazy[k]*(r-l+1);
}
LL query(int k,int l,int r,int add,int L,int R) {if (L<=l&&r<=R)return sum[k] + 1LL*(r-l+1)*add;add += lazy[k];int mid=(l+r)>>1;LL ans=0;if (L<=mid) ans += query(lson[k],l,mid,add,L,R);if (R >mid) ans += query(rson[k],mid+1,r,add,L,R);return ans;
}
int stamp = 0;
void init() {stamp=0;pid=0;
}
int main(){while (~ scanf("%d%d",&n,&q)) {init();for(int i=1;i<=n;i++) scanf("%d",&a[i]);build(root[0],1,n);int id = 0;for(int i=1;i<=q;i++){char op[2]; int l,r,t;scanf("%s",op);if(op[0] == 'C') {scanf("%d%d%d",&l,&r,&t);update(root[stamp],root[stamp+1],1,n,l,r,t);stamp ++;}if(op[0] == 'Q') {scanf("%d%d",&l,&r);LL ans = query(root[stamp],1,n,0,l,r); printf("%lld\n", ans);}if(op[0] == 'H') { scanf("%d%d%d",&l,&r,&t);LL ans = query(root[t],1,n,0,l,r);printf("%lld\n", ans);}if(op[0] == 'B'){scanf("%d",&t);stamp = t;}}}
}

一些练习

CF650D

题意:动态LIS,每次修改一个位置,每次操作查询LIS,操作相互独立

题解:

两种情况

第一种,更新后pos,出现在了LIS中

我们要做的是:查询[1,pos)中,h<h[pos]的所有数字,LISmax

可以对每一个前缀维护一个h的权值线段树,每个节点记录h在此值域内LISmax

第二种,更新后pos,没出现在LIS中

判断一下pos是否在存在于所有的,原序列LIS中。

这个地方很有趣。

hint: dp[i]+rev_dp[i]=LIS+1

Bonus:
1. 存在一个LIS包含元素i的条件
2. 所有LIS包含元素i的条件
#include <iostream>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;const int N = 400000+10;
const int INF = 1000000007;int bit[N];
vector<int> v;
int id(int x) {return lower_bound(v.begin(),v.end(),x)-v.begin()+1;
}
int get(int x) {int ans=0;while(x) {ans=max(ans,bit[x]);x-=x&-x;}return ans;
}
void upd(int pos,int x){while(pos<N) {bit[pos]=max(bit[pos],x);pos += pos&-pos;}
}
int n,m,h[N],dp[N],rdp[N],neccesary[N];
int LIS=0;
vector<int> pos[N];
void compress(int on) {v.clear();if (on == 0) {for(int i=1;i<=n;i++) v.push_back(h[i]);} else {for(int i=1;i<=n;i++) v.push_back(INF-h[i]);}sort(v.begin(), v.end());v.erase(unique(v.begin(),v.end()),v.end());
}
void LIS_Proccess() {scanf("%d%d",&n,&m);for(int i=1;i<=n;i++) {scanf("%d",&h[i]);}compress(0);for(int i=1;i<=n;i++) {dp[i] = get(id(h[i])-1) + 1;upd(id(h[i]), dp[i]);LIS = max(LIS, dp[i]);}memset(bit,0,sizeof(bit));compress(1);for(int i=n;i>=1;i--) {rdp[i] = get(id(INF-h[i])-1) + 1;upd(id(INF-h[i]), rdp[i]);}for(int i=1;i<=n;i++) {if (dp[i]+rdp[i] == LIS+1) {pos[dp[i]].push_back(i);}}for(int i=1;i<=n;i++) {if (pos[i].size() == 1) {neccesary[pos[i][0]] = 1;}}}int lson[N*22],rson[N*22],val[N*22],root[N*22],pid;
int ans[N], pre[N], suf[N], p[N], x[N];
void build(int &k,int l,int r) {k=++pid; val[k]=0;if(l==r) return;int mid=(l+r)>>1;build(lson[k],l,mid);build(rson[k],mid+1,r);
}
void change(int old,int &k,int l,int r,int pos,int x) {k=++pid;lson[k]=lson[old],rson[k]=rson[old],val[k]=max(x,val[old]);if(l==r) return;int mid=(l+r)>>1;if(pos<=mid) change(lson[old],lson[k],l,mid,pos,x);else change(rson[old],rson[k],mid+1,r,pos,x);
}
int query(int k,int l,int r,int L,int R) {if(L>R) return 0;if(L<=l&&r<=R) {return val[k];}int mid=(l+r)>>1;int ans=0;if (L<=mid) ans=max(ans, query(lson[k],l,mid,L,R));if (R >mid) ans=max(ans, query(rson[k],mid+1,r,L,R));return ans;
}int main() {LIS_Proccess();// neccesary[i]: 第i位一定出现在LIS中pid=0; compress(0);build(root[0],1,v.size());for(int i=1;i<=n;i++) {change(root[i-1],root[i],1,v.size(),id(h[i]),dp[i]);}for(int i=1;i<=m;i++) {scanf("%d%d",&p[i],&x[i]);ans[i] = neccesary[p[i]] ? LIS - 1 : LIS;pre[i] = query(root[p[i]-1], 1, v.size(), 1, id(x[i])-1);}//exit(0);pid=0; compress(1);build(root[n+1],1,v.size());for(int i=n;i>=1;i--) {change(root[i+1],root[i],1,v.size(),id(INF-h[i]),rdp[i]);}for(int i=1;i<=m;i++) {suf[i] = query(root[p[i]+1], 1, v.size(), 1, id(INF-x[i])-1);ans[i] = max(ans[i], pre[i]+suf[i]+1);printf("%d\n", ans[i]);}}

以上,于4/28,mark一下。

之后,待补的坑:

  • BIT套主席树 【学不会】
  • 主席树的区间更新【已补】

学数据结构是不可能学数据结构的,这辈子都不可能学数据结构!

转载于:https://www.cnblogs.com/RUSH-D-CAT/p/8965601.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/254733.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【转】小白级的CocoaPods安装和使用教程

原文网址&#xff1a;http://www.jianshu.com/p/e2f65848dddc 百度有很多CocoaPods的安装教程.第一次看的时候,确实有点摸不透的感觉.经过思考,一步一步来实践,前后花了三十几分钟,才顺利使用..所以想了想,我还是写一个小白级的教程吧.细到每一个细节都说明. 让你不用10分钟解决…

常见错误总结

少打头文件 少打using namespace std; 命名冲突&#xff0c;全局变量与局部变量命名一致&#xff0c;导致使用的值不是期望值 边读边写&#xff0c;导致改后读&#xff0c;覆盖写入的值 长整数移位溢出&#xff0c;1<<63是错误的&#xff0c;应该写成1ll<<63 循环变…

x264_sps_init

x264_sps_init此函数为序列量化集的初始化。主要对结构体x264_sps_t中参数的初始化。 void x264_sps_init( x264_sps_t *sps, int i_id, x264_param_t *param ) { sps->i_id i_id;首先设置序列参数集的ID b_qpprime_y_zero_transform_bypass判断码率控制方法是否是恒定质量…

HALCON相机标定相机内参相机外参

目录相机标定1.相机标定是什么2.怎么使用halcon进行相机内外参标定&#xff1f;&#xff08;1&#xff09;搭建硬件1.**相机连好电脑&#xff0c;用相机厂家软件打开相机&#xff0c;检查一下相机是否正常。**2.**接下来使用halcon连接相机**&#xff08;2&#xff09;开始标定…

ionic更改端口号

ionic serve -p 8888 —— 重新指定端口号为8888 serve [options] ............................... 启动本地服务器进行开发测试 dev/testing   [--consolelogs|-c] ..................... 输入app的控制台到ionic的控制台显示   [--serverlogs|-s] .....................…

angular change the url , prevent reloading

http://stackoverflow.com/questions/14974271/can-you-change-a-path-without-reloading-the-controller-in-angularjs $location.search({vln: $scope.vln_id}, false);会改变url中 &#xff1f; 后面的 搜索参数&#xff0c;但是controller不会重新实例化。angular 官方文档…

Ubuntu apt-get 更新/查看软件

ubuntu 升级软件&#xff1a; sudo apt-get update 更新源  sudo apt-get upgrade 更新已安装的包  sudo apt-get dist-upgrade 升级系统 ubuntu升级特定软件&#xff1a; 可以用 sudo apt-get install pkgname 看软件安装位置:dpkg -L xxxx 查看软件是否安装&#xff1…

X264设定

--aq-mode <integer> AQ method [1]- 0: Disabled- 1: Variance AQ (complexity mask)说明&#xff1a;自适应量化方法&#xff0c;可以改善某些场景过于模糊等问题&#xff0c;默认开启- 0: 关闭- 1: 可变AQ推荐值&#xff1a;默认范例&#xff1a;--aq-mode 1--aq-stre…

C#圆形卡尺测量程序基于halcon

废话不多说上源码 觉得帖子有用给点个赞哈 先来个效果图 下边的是源码&#xff0c;自己新建一个文件粘贴进去&#xff0c;包含到您现在的项目 中。这串源码后边是使用方法。 using System; using System.Collections.Generic; using System.Linq; using System.Text; usin…

MySQL松散索引扫描与紧凑索引扫描

什么是松散索引&#xff1f; 答&#xff1a;实际上就是当MySQL 完全利用索引扫描来实现GROUP BY 的时候&#xff0c;并不需要扫描所有满足条件的索引键即可完成操作得出结果。 要利用到松散索引扫描实现GROUP BY&#xff0c;需要至少满足以下几个条件&#xff1a;◆ GROUP BY 条…

算法马拉松24

算法马拉松24 A 小C的多边形 题意&#xff1a;n1个点的多边形。给外圈的边标记上1~n&#xff0c;里圈的边也标记上1~n&#xff0c;使得对于一个外圈相邻点与中间点构成的三角形的边权之和都相等。\(n \le 10^6\) 题解&#xff1a;显然每个三角形权值和为\(\frac{3(n1)}{2}\) 一…

HUD2795 线段树(单点更新)

题目中给出的h和w范围均大&#xff0c;其实n的最大范围才200000&#xff0c;所以我们建立的线段树大小为min(h,n),线段树的每一个节点包含一个变量c&#xff0c;记录当前区间内还剩下的可以put on的最大长度。插入一个数时&#xff0c;如果该数大于该区间最大值&#xff0c;则返…

科维PLC运行时系统ProConOS embedded CLR 2.2 特定应用

ProConOS embedded CLR是新型的开放式标准化PLC运行时系统&#xff0c;符合IEC 61131标准&#xff0c;可执行不同的自动化任务&#xff08;PLC、PAC、运动控制、CNC、机器人和传感器&#xff09;。   通过采用国际标准的微软中间语言&#xff08;依据IEC/ISO 23271标准为MSIL…

linux下vi命令大全

进入vi的命令 vi filename :打开或新建文件&#xff0c;并将光标置于第一行首 vi n filename &#xff1a;打开文件&#xff0c;并将光标置于第n行首 vi filename &#xff1a;打开文件&#xff0c;并将光标置于最后一行首 vi /pattern filename&#xff1a;打开文件&…

set()与get()详细解答(C#)

这几天在搬砖时候用到了set()与get()&#xff0c;同事问了我一些问题&#xff0c;我打算在博客中总结一下。 觉得帮助到了您&#xff0c;帮我点个赞哦。 属性访问器 其实说白了就是操作一个属性&#xff0c;更通俗一点说就是对一个变量的取值与赋值。 先来看get() get 访问…

IM应用中如何计算富文本的高度

背景 在开发IM的项目过程中&#xff0c;经常会有出现一些需要计算DOM高度&#xff0c;然后超出若干行隐藏等需求。很多时候&#xff0c;需要计算高度的DOM元素都是动态生成的&#xff0c;我们无法在数据渲染前获取到它的高度。 如果没有任何交互&#xff0c;我们可以通过CSS来实…

G代码 机器人的CNC实现

&#xfeff;  控制铣削工作台和工件的NC程序&#xff0c;通过CAD软件创建&#xff0c;这些NC程序与特定的机器类型相关。 NC程序在笛卡尔坐标系中动作的描述&#xff0c;对于需要确保一个明确的变换轴位置的关节型的机器人来说&#xff0c;缺少附加的状态和旋转信息。传…

IScroll5中文API整理,用法与参考

IScroll是移动页面上被使用的一款仿系统滚动插件。IScroll5相对于之前的IScroll4改进了许多&#xff0c;使得大家可以更方便的定制所需的功能了。 做项目的时候正好用到了这个插件&#xff0c;自己做了一下总结&#xff0c;发在这里方便大家学习IScroll5。 官网&#xff1a;htt…

Linux 安装USB摄像头

sudo apt-get updatesudo apt-get install fswebcamsudo apt-get install mplayersudo apt-get install alsamixer安装完毕ls /dev查找设备是否有video0这个设备sudo mplayer tv:// 可以看到摄像内容转载于:https://www.cnblogs.com/smartkeke/p/6820426.html