st表树状数组入门题单
预备知识
st表(Sparse Table)
主要用来解决区间最值问题(RMQ)以及维护区间的各种性质(比如维护一段区间的最大公约数)。
树状数组
- 单点更新
- 数组前缀和的查询
拓展:原数组是差分数组时,可进行区间更新,单点查询,当然想区间查询也有办法(维护两个树状数组即可)。
区别
树状数组用来维护一个具有区间可减(加)性质的工具,所以可以用来维护区间前缀和。
区间最值不具有区间可减性,所以不能使用树状数组进行维护,而使用st表。
st表的思想
- 初始化
预处理数组, f[x][i] 表示区间[x, x + (1 << i) ) 的最值
f[x][i] = min/max{f[x][i - 1], f[x + (1 << i - 1)][i - 1]}; // 1 << i - 1表示2的i-1次方
其实这里就是一个动态规划的思想 :
从x开始2的i次方的最值等于从 x 开始 (1 << i - 1) 范围内的最值加上从x + (1 << i - 1) 开始 (1 << i - 1)的最值
类似于2 = 1 + 1 - 询问区间内最值
对于询问区间[l, r], 设 d = [log2(r - l + 1)]
答案即为 : min/max{f[l][d], f[r - (1 << d) + 1][d]}
至于为什么,很显然从左边开始(1 << d)区间内,要是够不到,就再从右开始
树状数组
数组模拟树形结构, 没啥好说的
题单
ST表
(Poj 3264 Balanced Lineup)
对应知识点
- st表维护最值问题
想不出来查看思路
利用st表开两个数组维护最大值和最小值
代码
ヾ(•ω•`)o还是写不出来就点我吧
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;
typedef long long LL;
const int N = 5e4 + 10;
int n, q; // n : 数的数量 q : 询问数量
int dp_max[N][20]; // 存储区间最大值
int dp_min[N][20]; // 存储区间最小值
// st表初始化
void st_init()
{
for(int j = 1; j <= 21; j ++)
for(int i = 1; i + (1 << j) - 1 <= n; i ++) {
dp_max[i][j] = max(dp_max[i][j - 1], dp_max[i + (1 << (j - 1))][j - 1]);
dp_min[i][j] = min(dp_min[i][j - 1], dp_min[i + (1 << (j - 1))][j - 1]);
}
}
// st表询问区间内最大值及其最小值,得到它们的差
int query(int l, int r)
{
// int k = log2(r - l + 1);
int k=(int)(log(double(r-l+1)) / log(2.0));
int x = max(dp_max[l][k], dp_max[r - (1 << k) + 1][k]);
int y = min(dp_min[l][k], dp_min[r - (1 << k) + 1][k]);
return x - y;
}
// 快读模板,过题用
inline int read()
{
int x=0,f=1;char ch=getchar();
while (ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
while (ch>='0'&&ch<='9'){x=x*10+ch-48;ch=getchar();}
return x*f;
}
int main()
{
// ios::sync_with_stdio(false);
// cin >> n >> q;
n = read(), q = read();
for(int i = 1; i <= n; i ++)
{
int x;
x = read();
dp_max[i][0] = x;
dp_min[i][0] = x;
}
st_init();
for(int i = 1, l, r; i <= q; i ++)
{
l = read(), r = read();
printf("%d\n",query(l, r));
}
return 0;
}
(CF 1547F Array Stabilization (GCD version))
对应知识点
- st表维护区间内最大公约数 + 二分答案
题目大意
给定一个序列a,每次对每一对相邻的数求他们的最大公约数,并且保存为新的序列。Latex不会打,可能题意不清楚,可以移步去洛谷
解题核心
gcd(a,b,c)=gcd(gcd(a,b),c)
- 断环成链
- 二分查询区间最大长度
ヾ(•ω•`)o还是写不出来就点我吧
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;
typedef long long LL;
const int N = 400010;
int T, a[N];
int n;
LL g;
LL st[N][25];
LL gcd(int a, int b)
{
return b ? gcd(b, a % b) : a;
}
void st_init(){
for(int i = 1; i <= 2 * n; i ++) st[i][0] = a[i]; // 赋初值 + 断环成链
// st表维护区间最大公约数
for(int j = 1; (1 << j) <= 2 * n; j ++){
for(int i = 1; i + (1 << j) <= 2 * n; i ++){
st[i][j] = gcd(st[i][j - 1], st[i + (1 << j - 1)][j - 1]);
}
}
}
LL query(int l, int r){
int k = (int)(log(((double)(r - l + 1)))/log(2.0));
return gcd(st[l][k], st[r - (1 << k) + 1][k]);
}
bool check(int x){
for(int i = 1; i <= n; i ++){
if(g != query(i, i + x)) return false;
}
return true;
}
int main()
{
scanf("%d", &T);
while(T --)
{
scanf("%d", &n);
for(int i = 1; i <= n; i ++)
{
scanf("%d", &a[i]);
a[i + n] = a[i];
if(i == 1) g = a[i];
else g = gcd(g, a[i]);
}
st_init();
int l = 0, r = n - 1;
while(l < r){
int mid = (l + r) >> 1;
if(check(mid)) r = mid;
else l = mid + 1;
}
printf("%d\n", l);
}
return 0;
}
(CF1549D Integers Have Friends)
知识点同上题
这题需要用差分消去余数,剩下还是用st表维护最大公约数
ヾ(•ω•`)o还是写不出来就点我吧
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;
typedef long long LL;
const LL N = 200010;
LL a[N], b[N];
LL st[N][25];
LL n, T;
LL gcd(LL a, LL b){
return b ? gcd(b, a % b) : a;
}
LL query(LL l, LL r){
LL k = (LL)(log((double)(r - l + 1)) / log(2.0));
return gcd(st[l][k], st[r - (1 << k) + 1][k]);
}
int main()
{
scanf("%d", &T);
while(T --)
{
scanf("%d", &n);
for(LL i = 1; i <= n; i ++) scanf("%lld", &a[i]);
if(n == 1)
{
printf("1\n");
continue;
}
for(LL i = 1; i < n; i ++){
b[i] = a[i] - a[i + 1];
if(b[i] < 0) b[i] = - b[i];
}
n--;
for(LL i = 1; i <= n; i ++) st[i][0] = b[i];
for(LL j = 1; (1 << j) <= n; j ++)
for(LL i = 1; i + (1 << j) - 1 <= n; i ++)
st[i][j] = gcd(st[i][j - 1], st[i + (1 << j - 1)][j - 1]);
LL ans = 0;
for(LL i = 1, l, r; i <= n; i ++){
l = i, r = n;
while(l < r){
LL mid = l + r + 1 >> 1;
if(query(i, mid) == 1) r = mid - 1;
else l = mid;
}
if(b[i] != 1) ans = max(ans, l - i + 1);
}
printf("%lld\n", ans + 1);
}
return 0;
}
树状数组
(HDU 1166 敌兵布阵)
对应知识点
- 树状数组裸题(模板的使用)
ヾ(•ω•`)o还是写不出来就点我吧
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;
const int N = 50010;
int T, n;
char s[8];
int l, r;
int tr[N], a[N];
inline int read()
{
int x=0,f=1;char ch=getchar();
while (ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
while (ch>='0'&&ch<='9'){x=x*10+ch-48;ch=getchar();}
return x*f;
}
int lowbit(int x){
return x & -x;
}
void add(int k, int c){
while(k <= n) tr[k] += c, k += lowbit(k);
}
int ask(int x){
int res = 0;
while(x) res += tr[x], x -= lowbit(x);
return res;
}
int main()
{
T = read();
int cnt = 1;
while(T -- )
{
n = read();
printf("Case %d:\n", cnt ++);
for(int i = 1; i <= n; i ++){
a[i] = read();
add(i, a[i]);
}
while(1){
scanf("%s", s);
if(s[0] == 'Q'){
l = read(), r = read();
printf("%d\n", ask(r) - ask(l - 1));
}else if(s[0] == 'A'){
l = read(), r = read();
add(l, r);
}else if(s[0] == 'S'){
l = read(), r = read();
add(l, -r);
}else if(s[0] == 'E'){
break;
}
}
memset(tr, 0, sizeof(tr));
memset(a, 0, sizeof(a));
}
return 0;
}
(HDU 1754 I Hate It)
对应知识点
- 树状数组维护最大值(本来准备用st表的,但是有修改操作)
具体做法就是把树状数组维护的区间和变成区间最大值,一开始实在没有想到这么写
如果看不懂可以把这题的操作和树状数组最基本的两个操作代码对比起来看
ヾ(•ω•`)o还是写不出来就点我吧
#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>
using namespace std;
const int N = 2e6 + 10;
int lowbit(int x){
return x & -x;
}
inline int read()
{
int x=0,f=1;char ch=getchar();
while (ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
while (ch>='0'&&ch<='9'){x=x*10+ch-48;ch=getchar();}
return x*f;
}
int h[N], a[N];
int n, m;
// 树状数组维护最大值 时间复杂度O(logn^2)
void update(int x)
{
while(x <= n){
h[x] = a[x];
for(int i = 1; i < lowbit(x);i <<= 1)
h[x] = max(h[x], h[x - i]);
x += lowbit(x);
}
}
int query(int x, int y)
{
int ans = 0;
// 如果y-lowbit(y) >= x即直接取已经维护的h[y]最大值
// 否则逐个逼近最大值
while (y >= x)
{
ans = max(a[y], ans);
y --; // 已经取了第一个值
for (; y-lowbit(y) >= x; y -= lowbit(y))
ans = max(h[y], ans);
}
return ans;
}
char ch[3];
int main()
{
while(~scanf("%d%d", &n, &m)){
for(int i = 1; i <= n; i ++) a[i] = read(), update(i);
for(int i = 1, l, r; i <= m; i ++){
scanf("%s", ch);
l = read(), r = read();
if(ch[0] == 'Q')
{
printf("%d\n", query(l, r));
}else{
a[l] = r;
update(l);
}
}
}
return 0;
}
(Poj 3468 A Simple Problem with Integers)
对应知识点
- 树状数组的应用
这题较模板而言的不同之处是要进行区间修改以及区间和
对于区间修改我们简单的用树状数组维护一个差分数组
对于区间和,我们可以维护两个树状数组,b[i] 和 i * b[i] 利用这两个数组去取得区间和
(x + 1) * sum(Tr1, x) - sum(tr2, x)
ヾ(•ω•`)o还是写不出来就点我吧
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <cmath>
using namespace std;
const int N = 100010;
typedef long long LL;
int a[N];
LL Tr1[N], tr2[N];
int n, m;
int lowbit(int x){
return x & -x;
}
void add(LL tr[], int x, LL c){
while(x <= n) tr[x] += c, x += lowbit(x);
}
LL sum(LL tr[], int x){
LL res = 0;
while(x) res += tr[x], x-=lowbit(x);
return res;
}
LL prefix_sum(int x){
return (x + 1) * sum(Tr1, x) - sum(tr2, x);
}
int main()
{
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i ++) scanf("%d", &a[i]);
for(int i = 1; i <= n; i ++){
int b = a[i] - a[i - 1];
add(Tr1, i, b);
add(tr2, i, (LL)i * b);
}
while(m --){
char op[2];
int l, r, d;
scanf("%s%d%d", op, &l, &r);
if(*op == 'Q'){
printf("%lld\n", prefix_sum(r) - prefix_sum(l - 1));
}else{
scanf("%d", &d);
add(Tr1, l, d),add(tr2, l, (LL)l * d);
add(Tr1, r + 1, -d),add(tr2, r + 1, (LL)(r + 1)*(-d));
}
}
return 0;
}
(Poj 2182 Lost cows)
对应知识点
- 树状数组 + 二分
思路
tr数组存每个序号是否还有牛,故初始化为1即可。从后往前,牛的序号为前面比他小的再加上1
二分得到答案,二分的是比他小的牛的数量和
ヾ(•ω•`)o还是写不出来就点我吧
#include <iostream>
#define lowbit(x) x & (-x)
using namespace std;
const int N = 1e5;
int tr[N], pre[N], ans[N];
int n;
void add(int x, int k)
{
for(int i = x; i <= n; i += lowbit(i)) tr[i] += k;
}
int ask(int x)
{
int sum = 0;
for(int i = x; i; i -= lowbit(i)) sum += tr[i];
return sum;
}
int findx(int x)
{
int l = 1, r = n;
while(l < r)
{
int mid = l + r >> 1;
if(ask(mid) < x)
l = mid + 1;
else
r = mid;
}
return l;
}
int main()
{
cin >> n;
pre[1] = 0;
for(int i = 2; i <= n; i ++) cin >> pre[i];
for(int i = 1; i <= n; i ++){
tr[i] = lowbit(i);
// cout << tr[i] << ' ';
}
for(int i = n; i > 0; i --){
int x = findx(pre[i] + 1);
add(x, - 1);
ans[i] = x;
}
for(int i = 1; i <= n; i ++) cout << ans[i] << endl;
return 0;
}
(Poj 2299 Ultra-QuickSort)
过题
直接用归并排序
树状数组的后面学一手再补上来
ヾ(•ω•`)o还是写不出来就点我吧
#include <cstdio>
typedef long long LL;
const int N = 500010;
int n;
LL q[N], w[N];
LL merge_sort(int l, int r)
{
if (l == r) return 0;
int mid = l + r >> 1;
LL res = merge_sort(l, mid) + merge_sort(mid + 1, r);
int i = l, j = mid + 1, k = 0;
while (i <= mid && j <= r)
if (q[i] <= q[j]) w[k ++ ] = q[i ++ ];
else
{
res += mid - i + 1;
w[k ++ ] = q[j ++ ];
}
while (i <= mid) w[k ++ ] = q[i ++ ];
while (j <= r) w[k ++ ] = q[j ++ ];
for (i = l, j = 0; i <= r; i ++, j ++ ) q[i] = w[j];
return res;
}
int main()
{
while (scanf("%d", &n), n)
{
for (int i = 0; i < n; i ++ ) scanf("%d", &q[i]);
printf("%lld\n", merge_sort(0, n - 1));
}
return 0;
}
写到最后
第一次写博客,如果有不足请提出,我很乐意听取意见。
现在处于一个大量知识的学习时间段,希望博客能够让我所学积淀下来。
感谢观看( ̄︶ ̄*)) 完结撒花
st表树状数组入门题单的更多相关文章
- POJ 2299 Ultra-QuickSort 求逆序数 (归并或者数状数组)此题为树状数组入门题!!!
Ultra-QuickSort Time Limit: 7000MS Memory Limit: 65536K Total Submissions: 70674 Accepted: 26538 ...
- HDU 1166 敌兵布阵(线段树/树状数组模板题)
敌兵布阵 Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others) Total Submi ...
- bzoj1103树状数组水题
(卧槽,居然规定了修改的两点直接相连,亏我想半天) 非常水的题,用dfs序(而且不用重复,应该是直接规模为n的dfs序)+树状数组可以轻松水 收获:树状数组一遍A(没啥好骄傲的,那么简单的东西) #i ...
- UESTC 1584 Washi与Sonochi的约定【树状数组裸题+排序】
题目链接:UESTC 1584 Washi与Sonochi的约定 题意:在二维平面上,某个点的ranked被定义为x坐标不大于其x坐标,且y坐标不大于其y坐标的怪物的数量.(不含其自身),要求输出n行 ...
- 敌兵布阵 HDU - 1166 (树状数组模板题,线段树模板题)
思路:就是树状数组的模板题,利用的就是单点更新和区间求和是树状数组的强项时间复杂度为m*log(n) 没想到自己以前把这道题当线段树的单点更新刷了. 树状数组: #include<iostrea ...
- poj2299树状数组入门,求逆序对
今天入门了树状数组 习题链接 https://blog.csdn.net/liuqiyao_01/article/details/26963913 离散化数据:用一个数组来记录每个值在数列中的排名,不 ...
- 树状数组训练题1:弱弱的战壕(vijos1066)
题目链接:弱弱的战壕 这道题似乎是vijos上能找到的最简单的树状数组题了. 原来,我有一个错误的思想,我的设计是维护两个树状数组,一个是横坐标,一个是纵坐标,然后读入每个点的坐标,扔进对应的树状数组 ...
- POJ 2352 stars (树状数组入门经典!!!)
Stars Time Limit: 1000MS Memory Limit: 65536K Total Submissions: 54352 Accepted: 23386 Descripti ...
- 树状数组 简单题 cf 961E
题目链接 : https://codeforces.com/problemset/problem/961/E One day Polycarp decided to rewatch his absol ...
随机推荐
- IOS 集成 Bilibili IJKPlayer播放器,播放rtmp视频流
因为公司项目需要,我一个连iPhone都没用过的人竟然跑去开发iOS APP.近一段时间一直忙于赶项目,到今天差不多了,所以记录一下当时遇到的各种坑,先从ios 集成 ijkplayer播放器说起! ...
- 从需求去理解 Linux dbus与基于dbus协议的无agent软件管理
What is IPC IPC [Inter-Process Communication] 进程间通信,指至少两个进程或线程间传送数据或信号的一些技术或方法.在Linux/Unix中,提供了许多IPC ...
- 事务保存点savepoint
一.
- Input 只能输入数字,数字和字母等的正则表达式
JS只能输入数字,数字和字母等的正则表达式 1.文本框只能输入数字代码(小数点也不能输入) <input onkeyup="this.value=this.value.replace( ...
- IDE集成管理Tomcat的基本原理
知道IDE是怎样控制Tomcat的,对更清晰地理解Java Web的执行过程有帮助.在此以IntelliJ IDEA为例,简要描述一下IDE集成管理Tomcat的基本原理. 首先是两个重要的环境变量: ...
- 密码学系列之:bcrypt加密算法详解
目录 简介 bcrypt的工作原理 bcrypt算法实现 bcrypt hash的结构 hash的历史 简介 今天要给大家介绍的一种加密算法叫做bcrypt, bcrypt是由Niels Provos ...
- 关联数组VS索引数组
关联数组和常规说的数组类似,它包含标量抄数据,可用索引值来单独选择这些数据,和常规数组不同的是, 关联数组的索引值不是非负的整数而是任意的标量袭.这些标量称为百Keys,可以在以后用于检索数组中的数值 ...
- php curl发送数据和文件
function mycurl($file, $url, $aid) { // 如果文件名是中文名,将中文字符编码转换一下 $file=iconv("UTF-8","gb ...
- lumen-phpunit 单元测试
lumen-框架5.8为例 1,把vendor下的bin目录放到环境变量里面: 2,设置路由 $router->get('syn', ['uses' => 'syn\syn@diction ...
- git 要求密码的解决方法:【生成gitLab公钥】:以及如何配置GitLab中的SSH key
参考链接: https://www.cnblogs.com/yjlch1016/p/9692840.html https://blog.csdn.net/u011925641/article/deta ...