前言
查询分为在线和离线两种,在离线查询中数据是不进行更改操作的,可以使用一些较为暴力的方法,但是在线查询中,由于数据也需要实时变更,需要一些更为巧妙的方法降低更改操作的复杂度。本篇博客的两个部分均为在线查询,在查询的过程中亦有更新操作,分块思想中有实时插入删除操作,树状数组中有实时更新操作,那么该如何降低更新操作的复杂度呢?
Part one:分块思想
问题引入:给出一个非负整数序列A,在有可能随时添加或删除元素的情况下,实时查询序列元素第k大的数。如果用暴力,我们会发现,每次添加或者删除都需要重新进行排序,赋予每个元素新的排位,十分的麻烦,由此引入分块思想,先分为多个集合处理,找临界集合再细致到元素
一、算法思想
- 该题属于在线查询,如果直接暴力去做,则插入或者删除元素十分难以处理,时间复杂度高
- 可以把所有可能范围的值分为多块,比如最大范围为N,则可将此分为sqrt(N)上取整块,则可以知道前sqrt(N)上取整-1块中的范围都是sqrt(N)下取整
- 这样分有什么好处:当需要找第k大的数的时候先累加块中元素数,到临界条件后,再取临界块中一一查找块中元素即可得到该元素是什么
- 具体如何实现:开一个block数组存块中元素,开一个table数组存每个元素个数
二、算法代码
问题描述:给出一个栈,实现Push(入栈)、Pop(出栈,并输出出栈的数)、PeekMedian(输出栈中间大小的数),当没有元素的时候Pop和PeekMedian都应该输出Invalid(范围:10e5)
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 10;
const int sqrN = 316; //sqrt(1e5+1)下取整
int block[sqrN];
int table[maxn];
stack<int> st;
void peekMedian(int k)
{
int sum = 0;
int idx = 0;
while(sum + block[idx] < k)
sum += block[idx ++ ];
int num = idx * sqrN;
while(sum + table[num] < k)
sum += table[num ++ ];
cout << num << endl;
}
void Push(int x)
{
st.push(x);
block[x / sqrN] ++ ;
table[x] ++ ;
}
void Pop()
{
int x = st.top();
st.pop();
block[x / sqrN] -- ;
table[x] -- ;
cout << x << endl;
}
int main()
{
ios_base::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int x, n;
memset(block, 0, sizeof(block));
memset(table, 0, sizeof(table));
string cmd;
cin >> n;
while(n -- )
{
cin >> cmd;
if(cmd.compare("Push") == 0)
{
cin >> x;
Push(x);
}
else if(cmd.compare("Pop") == 0)
{
if(st.empty() == true) cout << "Invalid" <<endl;
else Pop();
}
else
{
if(st.empty() == true) cout << "Invalid" <<endl;
else
{
int k = (st.size() + 1) / 2;
peekMedian(k);
}
}
}
return 0;
}
Part two:树状数组
问题引入:对于一个数组,查询区间和,且在此过程中有更新操作,比如将数组某个元素增加一定的值。我们可以看出,如果用前缀和思想来暴力做,每次元素更新后需要更新的前缀和十分多,时间复杂度很高,所以我们可以采取树状数组的策略,下面将会讲述树状数组是什么以及如何用(单点更新,区间查询)
一、算法思想
- lowbit(x) = x & -x,等价于取x的二进制最右边的1和它右边所有的0,结果一定是2的倍数(需要自己去define)
- 什么是树状数组:树状数组是用来记录和的数组,它存放i以及i前lowbit(i) - 1个整数的和,即树状数组一个单元C[i]的覆盖长度为lowbit(i)
- 为了得出某个区间和,我们需要实现函数getSum(x),得到前x个整数的和;为了实现在线查询进行实时更改,我们需要实现函数update(x,v),在第x个数上加上v
- getSum(x):我们可以从此处往前遍历,每次覆盖lowbit(i)即可,由此不断更新i
- update(x,v):我们可以从此处往后遍历,由于要使得跨越的范围a满足,lowbit(x+a)>lowbit(x)才可以覆盖,显然最小的a == lowbit(x)
二、算法代码
问题描述:给定一个有N个正整数的序列A(N<=10e5,A[i]<=10e5),对序列中的每个数,求出序列中它右边比它小的数的个数
- 非离散:
#include<bits/stdc++.h>
#define lowbit(i) ((i)&(-i))
using namespace std;
const int maxn = 100010;
int c[maxn]; //树状数组
void update(int x, int v)
{
for(int i = x; i < maxn; i += lowbit(i))
c[i] += v;
}
int getSum(int x)
{
int sum = 0;
for(int i = x; i > 0; i -= lowbit(i))
sum += c[i];
return sum;
}
int main()
{
ios_base::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int n, x;
cin >> n;
memset(c, 0, sizeof(c));
for(int i = 0; i < n; i ++ )
{
cin >> x;
update(x, 1);
cout << getSum(x - 1) << endl;
}
return 0;
}
- 离散化:
- 如果A[i]范围改为<=10e9,数据太大,会导致c树状数组爆内存,所以可以将数组离散化存储,核心思想就是将原本十分发散的下标聚集起来,在此题中离散处理输入后用了一个新的数组A来存储输入(非离散情况,值即为下标非常发散,离散化后将下标聚集)
#include<bits/stdc++.h>
#define lowbit(i) ((i)&(-i))
using namespace std;
const int maxn = 100010;
int a[maxn], c[maxn]; //离散化后数组和树状数组
struct Node
{
int val; //元素值,在非离散情况下是下标
int pos; //下标,离散化赋予其新下标
bool operator < (const Node &W)const //重载小于号
{
return val < W.val;
}
} temp[maxn];
void update(int x, int v)
{
for(int i = x; i < maxn; i += lowbit(i))
c[i] += v;
}
int getSum(int x)
{
int sum = 0;
for(int i = x; i > 0; i -= lowbit(i))
sum += c[i];
return sum;
}
int main()
{
ios_base::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int n;
cin >> n;
memset(c, 0, sizeof(c));
//将输入离散化
for(int i = 0; i < n; i ++ )
{
cin >> temp[i].val;
temp[i].pos = i;
}
sort(temp, temp + n);
for(int i = 0; i < n; i ++ )
{
if(i == 0 || temp[i].val != temp[i - 1].val)
a[temp[i].pos] = i + 1;
else
a[temp[i].pos] = a[temp[i - 1].pos];
}
//树状数组操作区间
for(int i = 0; i < n; i ++ )
{
update(a[i], 1);
cout << getSum(a[i] - 1) << endl;
}
return 0;
}
三、算法进阶
- 上面我们做到了单点更新、区间查询,如果想要进行区间更新、单点查询该如何做呢?
- 上面我们树状数组存的是区间和,此处我们存区间更新累加值,即这一段被加了多少值
- 单点查询getSum(x):即可直接套用上一题的update,累加所有包含此元素的累加值进行累加
- 区间更新update(x,v):即可套用上一题的getSum寻找完美覆盖,让此覆盖包含的几个小覆盖区间都累加即可
int getSum(int x)
{
int sum = 0;
for(int i = x; i < maxn; i += lowbit(i))
sum += c[i];
return sum;
}
void update(int x, int v)
{
for(int i = x; i > 0; i -= lowbit(i))
c[i] += v;
}