预备知识

  1. st表(Sparse Table)

    主要用来解决区间最值问题(RMQ)以及维护区间的各种性质(比如维护一段区间的最大公约数)。

  2. 树状数组

    • 单点更新
    • 数组前缀和的查询

    拓展:原数组是差分数组时,可进行区间更新,单点查询,当然想区间查询也有办法(维护两个树状数组即可)。

区别

树状数组用来维护一个具有区间可减(加)性质的工具,所以可以用来维护区间前缀和。

区间最值不具有区间可减性,所以不能使用树状数组进行维护,而使用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表树状数组入门题单的更多相关文章

  1. POJ 2299 Ultra-QuickSort 求逆序数 (归并或者数状数组)此题为树状数组入门题!!!

    Ultra-QuickSort Time Limit: 7000MS   Memory Limit: 65536K Total Submissions: 70674   Accepted: 26538 ...

  2. HDU 1166 敌兵布阵(线段树/树状数组模板题)

    敌兵布阵 Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others) Total Submi ...

  3. bzoj1103树状数组水题

    (卧槽,居然规定了修改的两点直接相连,亏我想半天) 非常水的题,用dfs序(而且不用重复,应该是直接规模为n的dfs序)+树状数组可以轻松水 收获:树状数组一遍A(没啥好骄傲的,那么简单的东西) #i ...

  4. UESTC 1584 Washi与Sonochi的约定【树状数组裸题+排序】

    题目链接:UESTC 1584 Washi与Sonochi的约定 题意:在二维平面上,某个点的ranked被定义为x坐标不大于其x坐标,且y坐标不大于其y坐标的怪物的数量.(不含其自身),要求输出n行 ...

  5. 敌兵布阵 HDU - 1166 (树状数组模板题,线段树模板题)

    思路:就是树状数组的模板题,利用的就是单点更新和区间求和是树状数组的强项时间复杂度为m*log(n) 没想到自己以前把这道题当线段树的单点更新刷了. 树状数组: #include<iostrea ...

  6. poj2299树状数组入门,求逆序对

    今天入门了树状数组 习题链接 https://blog.csdn.net/liuqiyao_01/article/details/26963913 离散化数据:用一个数组来记录每个值在数列中的排名,不 ...

  7. 树状数组训练题1:弱弱的战壕(vijos1066)

    题目链接:弱弱的战壕 这道题似乎是vijos上能找到的最简单的树状数组题了. 原来,我有一个错误的思想,我的设计是维护两个树状数组,一个是横坐标,一个是纵坐标,然后读入每个点的坐标,扔进对应的树状数组 ...

  8. POJ 2352 stars (树状数组入门经典!!!)

    Stars Time Limit: 1000MS   Memory Limit: 65536K Total Submissions: 54352   Accepted: 23386 Descripti ...

  9. 树状数组 简单题 cf 961E

    题目链接 : https://codeforces.com/problemset/problem/961/E One day Polycarp decided to rewatch his absol ...

随机推荐

  1. etcd学习(8)-etcd中Lease的续期

    etcd中的Lease 前言 Lease Lease 整体架构 key 如何关联 Lease Lease的续期 过期 Lease 的删除 checkpoint 机制 总结 参考 etcd中的Lease ...

  2. stat 命令家族(4)- 详解 iostat

    性能测试必备的 Linux 命令系列,可以看下面链接的文章哦 https://www.cnblogs.com/poloyy/category/1819490.html 介绍 报告 CPU 信息和 I/ ...

  3. 简单内存池的C实现

    1. 序言 对于程序开发人员来说,会经常听到这种"池"的概念,例如"进程池","线程池","内存池"等,虽然很多时没有吃 ...

  4. python爬区csdn文章信息(原始稿)

    使用python对csdn的博主文章进行爬取,期间又遇到了新的问题和旧的已经遇到过的问题.首先做一个笔记,免得以后遇到同样的问题时还得浪费时间和经历. 刚开始目的没那么明确,主要在于熟悉相关的规则及流 ...

  5. Docker 容器间的单向连接

    Docker 容器间的单向连接 前言 a. 本文主要为 Docker的视频教程 笔记. b. 环境为 CentOS 7.0 云服务器 c. 上一篇:Dockerfile 自动制作 Docker 镜像( ...

  6. Groovy系列(1)- Groovy简述

    Groovy简述 前言 由于性能测试的JSR223 Sampler取样器需要用到 Groovy 语言,这两天对其进行了粗略的学习,本文是对学习做的一个简单总结,主要内容参考于官方文档(Groovy 的 ...

  7. 关于jQ的小案例分享

    <!DOCTYPE html><html> <head> <meta charset="utf-8"> <title>表 ...

  8. python学习笔记(十二)-网络编程

    本文结束使用 Requests 发送网络请求.requests是一个很实用的Python HTTP客户端库,编写爬虫和测试服务器响应数据时经常会用到.可以说,Requests 完全满足如今网络的需求. ...

  9. xadmin使用富文本

    环境:pycharm django1.11.20 python2.7 后台xadmin(根据网络各种资料实现) 本教程接上篇如何安装 xadmin,如何不清楚,请看上一篇(django安装xadmin ...

  10. P5666-[CSP-S2019]树的重心【树状数组】

    正题 题目链接:https://www.luogu.com.cn/problem/P5666 题目大意 给出\(n\)个点的一棵树,对于每条边割掉后两棵树重心编号和. \(1\leq T\leq 5, ...