预备知识

  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. Spring整合MyBatis小结

    MyBatis在Spring中的配置 我们在Spring中写项目需要运用到数据库时,现在一般用的是MyBatis的框架来帮助我们书写代码,但是学习了SSM就要知道M指的就是MyBatis,在此,在Sp ...

  2. .NET5修改配置不重启自动生效

    .NET Core,.NET5默认配置都是只加载一次,修改配置时都需要重启才能生效,如何能修改即时生效呢,下面来演示一遍. 一.设置配置文件实时生效 1.1配置 在Program.cs的CreateH ...

  3. wpf内存泄漏问题

    http://www.cnblogs.com/Cindys/archive/2012/05/17/2505893.html http://blogs.msdn.com/b/jgoldb/archive ...

  4. liunx常见指令

    linux目录结构 bin:存储普通命令 sbin:存储超级命令 home:存储普通用户 root:存储超级用户 usr /usr/local:下存储数据或软件,通常软件都放在其中 tmp:临时目录 ...

  5. 数据结构逆向分析-Vector

    数据结构逆向分析-Vector 这个应该是家喻户晓了的东西把,如果说C/C++程序员Vector都不用的话,可能就是一个不太好的程序员. Vector就是一个STL封装的动态数组,数组大家都知道是通过 ...

  6. uni-app跨平台框架介绍和快速入门

    前言: 首先今天主要介绍的是一个多平台的前端框架uni-app,关于多平台的前端框架网上有很多成熟的解决方案比如说Taro,React Native,Flutter等这些都是一些非常优秀的前端跨平台的 ...

  7. 谷歌浏览器chrome安装插件报"程序包无效: CRX_HEADER_INVALID"错误

    今天参加需求评审,看到原来可以谷歌浏览器查看Axure原型文件,真是只有想不到,没有做不到(自己孤陋寡闻了,第一次接触Axure). 需求评审后,我百度"如何使用谷歌浏览器查看Axure原型 ...

  8. python pip 安装使用国内镜像源

    国内镜像源 清华:https://pypi.tuna.tsinghua.edu.cn/simple 阿里云:http://mirrors.aliyun.com/pypi/simple/ 中国科技大学 ...

  9. centos7安装部署SVN

      SVN介绍 SVN是个自由.开源的版本控制系统,绝大多数开源软件都使用SVN作为代码版本管理软件. SVN的官方网站http://subversion.apache.org/.目前SVN在开源社区 ...

  10. 定要过python二级 选择题第5套

    1. 2.. 3. https://zhidao.baidu.com/question/1952133724016713828.html 4. 5. 6. 7. 8. 9. 10. 11.