数列分块:从入门到跑路——数列分块入门九题
第一题
区间加,单点询问
首先讲讲数列分块是个啥。
我们把数列分成一个个块,每个数属于块中的一部分。
对于整块,我们有复杂度优秀的操作(一般是 \(O(1)\) ),对于散块,我们暴力操作。
如果块长为 \(\sqrt n\),那么复杂度就是区间中散块长度+整块个数,复杂度为 \(O(n\sqrt n)\)。
然后回到这道题。对于整块,每次加时我们都往整块标记上加,散块我们直接往权值上加,询问时再把权值加上标记。总复杂度和上面推导一致,为 \(O(n\sqrt n)\)。
第二题
区间加,询问小于 \(x\) 的元素个数
如果序列有序,我们可以二分解决。因此我们尝试让每个块有序。
对于整块加,加完后还是有序的,可以打标记,最后再加就好。
对于散块,加完后不一定有序了,需要对散块重新排序。
看起来十分暴力,但是每次我们只会对散块排序,每次最大对 \(O(2\sqrt n)\) 排序,所以最大就是 \(O(n\sqrt n log(n \sqrt n))\)。可以通过此题。
第三题
区间加,询问前驱
第二题的 pro 版,改变一下二分得到的答案即可。
第四题
区间加,求区间和
第一题的 plus 版,要做的是额外记录整块权值和,把询问改成散块暴力+整块区间和就行。
第五题
区间开方,区间和
开方很少的次数后,数就会变成 \(1\),如果块内所有数都变成了 \(1\),这个块就不会再变了。
如果一整个块都变成了 \(1\),我们就可以 \(O(1)\) 修改与询问了。
如果还有数不是 \(1\),我们就整块打懒标记。最多只会有 31 个懒标记,如果记录最大值(用不着更改)还能更少。
第六题
单点插入,单点询问,数据随机
我们把每个块变成链表,方便插入,块之间也用链表相连,原因之后讲。
我们查找当前插入属于哪个块,插完后块大小 \(+1\)。如果块过大,对于块内询问不利。因此当块大过一个值时我们要把一个块裂成两个块。由于块之间由链表相连,所以实现简单。
第七题
区间加,区间乘,区间和
第四题的 plus 版,只需要额外考虑乘法对加法标记的影响。具体可参考 线段树2。
第八题
询问有多少数等于 \(c\),询问后将区间的所有数改为 \(c\)。
如果一整个块都是同一个数,那么只需要 \(O(1)\) 就可以进行询问与修改。
于是可以发现这是第五题的 plus 版。如果有散块修改要把整块的状态改成不相同。
第九题
区间众数
恭迎数列分块入门题真神。。。其实也没那么难。
离散化肯定是要的。
我们记录从第 \(i\) 个块到第 \(j\) 个块的众数,这个只需要 \(\sqrt n \sqrt n \sqrt n = n \sqrt n\) 的时间就可以得出。
然后我们再前缀和一下,记录从第 \(1\) 个块到第 \(i\) 个块的权值为 \(j\) 的点有多少个。
对于每个询问,我们把整块的众数求出来,对于散块中出现的每个数,加上它在整块中出现的次数,与现在的众数干一架。
散块中没出现的数,又不是众数的数呢?她不是整块的众数,又没有再次出现,肯定出现次数还是比原来整块的众数少的。就不用考虑了。
#include<bits/stdc++.h>
#define pii pair<int, int>
#define mp make_pair
#define ll long long
using namespace std;
const ll maxn = 4e4 + 5;
ll n, m;
ll a[maxn];
ll len;
ll col[maxn], si[maxn], val[maxn], lazy[maxn];
ll L[maxn], R[maxn];
ll zhong[505][505], b[maxn], p[505][maxn], ton[maxn];
pii w[maxn];
int pei[maxn];
bool cmp(pii a, pii b)
{
return a.first < b.first;
}
int que(int l, int r)
{
if(col[l] == col[r])
{
int mx = 0;
for(int i = l;i <= r;i ++)
{
b[val[i]] ++;
if(b[mx] < b[val[i]] || (b[val[i]] == b[mx] && val[i] < mx)) mx = val[i];
}
for(int i = l;i <= r;i ++) b[val[i]] = 0;
return mx;
}
int mx = (col[r] - 1 >= col[l] + 1 ? zhong[col[l] + 1][col[r] - 1] : 0), mxd = 0;
if(mx) mxd = p[col[r] - 1][mx] - p[col[l]][mx];
vector<int> cun;
for(int i = l;col[i] == col[l];i ++)
{
// cout << val[i] << ' ';
if(val[i] == mx)
{
mxd ++;
continue;
}
b[val[i]] ++;
if(b[val[i]] == 1) cun.push_back(val[i]);
}
for(int i = r;col[i] == col[r];i --)
{
if(val[i] == mx)
{
mxd ++;
continue;
}
b[val[i]] ++;
if(b[val[i]] == 1) cun.push_back(val[i]);
}
for(auto i : cun)
{
int big = b[i] + (col[r] - 1 >= col[l] + 1 ? p[col[r] - 1][i] - p[col[l]][i] : 0);
if(big > mxd || (big == mxd && i < mx)) mx = i, mxd = big;
}
int ans = mx;
for(int i = l;col[i] == col[l];i ++) b[val[i]] = 0;
for(int i = r;col[i] == col[r];i --) b[val[i]] = 0;
return ans;
}
signed main()
{
freopen("text.in", "r", stdin);
freopen("text.out", "w", stdout);
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
cin >> n >> m;
for(ll i = 1;i <= n;i ++) cin >> w[i].first, w[i].second = i;
sort(w + 1, w + n + 1, cmp);
int tot = 0, last = -1;
for(int i = 1;i <= n;i ++)
{
if(last != w[i].first) last = w[i].first, tot ++;
pei[tot] = w[i].first;
val[w[i].second] = tot;
}
len = sqrt(n);
ll sp = 1, siz = 0;
for(ll i = 1;i <= n;i ++)
{
if(siz == len)
{
R[sp] = i - 1, si[sp] = siz;
siz = 0, sp ++, L[sp] = i;
}
siz ++;
col[i] = sp;
}
si[sp] = siz;
L[1] = 1, R[sp] = n;
last = 1;
for(int i = 1;i <= n;i ++)
{
if(last != col[i])
{
i --;
for(int j = 1;j <= n;j ++) p[col[i]][j] = b[j];
last = col[i];
i ++;
}
b[val[i]] ++;
}
for(int i = 1;i <= sp;i ++)
{
int mx = 0;
for(int j = 1;j <= n;j ++) b[j] = 0;
for(int j = L[i];j <= R[i];j ++)
{
b[val[j]] ++;
if(b[val[j]] > b[mx] || (b[val[j]] == b[mx] && val[j] < mx)) mx = val[j];
}
zhong[i][i] = mx;
for(int j = i + 1;j <= sp;j ++)
{
for(int k = L[j];k <= R[j];k ++)
{
b[val[k]] ++;
if(b[val[k]] > b[mx] || (b[val[k]] == b[mx] && val[k] < mx)) mx = val[k];
}
zhong[i][j] = mx;
}
}
for(int i = 1;i <= n;i ++) b[i] = 0;
int l, r, x = 0;
for(int _ = 1;_ <= m;_ ++)
{
cin >> l >> r;
l = ((l + x - 1) % n) + 1;
r = ((r + x - 1) % n) + 1;
if(l > r) swap(l, r);
x = pei[que(l, r)];
cout << x << endl;
}
return 0;
}