分块练习整理
定义
分块,无非就是将长为
n
n
n 的序列分为长度为
n
\sqrt{n}
n 的若干小块,以每次处理或查询时提高效率。说白了就是优雅的暴力。
块内排序
一、Problem G: [loj6278]数列分块入门 2
给出一个长为 n n n 的数列,以及 n n n 个操作,操作涉及区间加法,询问区间内小于某个值 x x x 的元素个数。
思路
考虑到查询,就要使块内有序,即维护块的单调性。
预处理:逐一对块内排序,复杂度 O(nlogn) \text{O(nlogn)} O(nlogn)。
所以修改:
-
散块:暴力,逐一加上,然后重新排序以维护单调性;
-
整块:修改 lazytag \text{lazytag} lazytag,不用再次排序。
最后,查询:
-
散块:对每个元素逐一判断、统计即可。
-
整块:二分思想,因为有序所以直接 lowerbound \text{lowerbound} lowerbound 即可。
这样总复杂度就是 O ( n l o g n + n n l o g n ) O(nlogn +n \sqrt{n} log \sqrt{n}) O(nlogn+nnlogn)。
代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define maxn 50005
#define maxm 750
#define rep(i, a, b) for(int i = a; i <= b; ++i)
int n, val[maxn];
int lz[maxm], blk[maxn], len;
vector <int> v[maxm];
int opt, a, b, c;
inline int gb(int x)
{
int res = x / len;
if(x % len) res++;
return res;
}
inline void reset(int x)
{
v[x].clear();
rep(i, (x - 1) * len + 1, x * len)
v[x].push_back(val[i]);
sort(v[x].begin(), v[x].end());
}
inline void updt(int l, int r, int k)
{
rep(i, l, min(r, gb(l) * len))
val[i] += k;
reset(gb(l));
if(gb(l) != gb(r))
{
rep(i, (gb(r) - 1) * len + 1, r)
val[i] += k;
reset(gb(r));
}
rep(i, gb(l) + 1, gb(r) - 1)
lz[i] += k;
}
inline void qry(int l, int r, int k)
{
int ans = 0;
rep(i, l, min(r, gb(l) * len))
if(val[i] + lz[gb(i)] < k) ans += 1;
if(gb(l) != gb(r))
rep(i, (gb(r) - 1) * len + 1, r)
if(val[i] + lz[gb(i)] < k) ans += 1;
rep(i, gb(l) + 1, gb(r) - 1)
ans += lower_bound(v[i].begin(), v[i].end(), k - lz[i]) - v[i].begin();
printf("%lld\n", ans);
}
signed main()
{
scanf("%lld", &n);
len = sqrt(n);
rep(i, 1, n) scanf("%lld", &val[i]);
rep(i, 1, n)
v[gb(i)].push_back(val[i]);
rep(i, 1, (n - 1) / len + 1)
sort(v[i].begin(), v[i].end());
rep(i, 1, n)
{
scanf("%lld %lld %lld %lld", &opt, &a, &b, &c);
if(opt) qry(a, b, c * c);
else updt(a, b, c);
}
return 0;
}
二、P5356 [Ynoi2017] 由乃打扑克
一道搞了我一上午加一中午的题。
给定长为 n n n 的序列,操作包含区间加和求区间第 k k k 小的数。
思路
和上一题一样,要维护块内的单调性。
所以对于预处理和区间加的操作一样。
查询:
二分,因为这题值域较小,所以 r \text{r} r 取区间 [ l , r ] [l,r] [l,r] 的最大者, l l l 取区间 [ l , r ] [l,r] [l,r] 最小值即可。每次使用 check \text{check} check 函数检查当前 mid \text{mid} mid 值在区间 [ l , r ] [l,r] [l,r] 中的排名,直到找到答案。
check \text{check} check 函数:
每次给定区间范围 [ l , r ] [l,r] [l,r] 和当前 mid \text{mid} mid 值。
-
散块:逐一遍历,统计 ≤ mid \leq \text{mid} ≤mid 的值;
-
整块:因为块内有序,所以使用二分找到符合条件的数的数量即可。
-
注意:对于上述整块的遍历,要添加一些特判以减少时间;不要为了省码量用 upperbound \text{upperbound} upperbound,会 TLE \text{TLE} TLE。
代码
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
#define int long long
#define maxn 1000010
#define maxm 500010
#define rep(i, a, b) for(int i = a; i <= b; ++i)
int n, m, a[maxn];
int opt, l, r, k;
int len = 150, tot;
vector <int> v[maxm];
int lz[maxm];
inline int get(int x)
{
int res = x / len;
if(x % len != 0) res++;
return res;
}
inline void reset(int x)
{
v[x].clear();
rep(i, (x - 1) * len + 1, min(n, x * len))
v[x].push_back(a[i]);
sort(v[x].begin(), v[x].end());
}
inline void updt(int l, int r, int k)
{
int posl = get(l), posr = get(r);
if(posl == posr)
{
rep(i, l, r) a[i] += k;
reset(posl);
return;
}
rep(i, l, posl * len)
a[i] += k;
reset(posl);
rep(i, (posr - 1) * len + 1, r)
a[i] += k;
reset(posr);
rep(i, posl + 1, posr - 1)
lz[i] += k;
}
inline int check(int l, int r, int mid)
{
int cnt = 0, posl = get(l), posr = get(r);
if(posl == posr)
{
rep(i, l, r)
if(a[i] + lz[posl] <= mid) cnt += 1;
return cnt;
}
rep(i, l, posl * len)
if(a[i] + lz[posl] <= mid) cnt++;
rep(i, (posr - 1) * len + 1, r)
if(a[i] + lz[posr] <= mid) cnt++;
rep(i, posl + 1, posr - 1)
{
int L = 0, R = len - 1;
if (v[i][0] + lz[i] > mid) continue;
if(v[i][len - 1] + lz[i] <= mid)
{
cnt += len;
continue;
}
while(L < R)
{
int Mid = (L + R) / 2 + 1;
if(v[i][Mid] + lz[i] <= mid) L = Mid;
else R = Mid - 1;
}
if(v[i][L] + lz[i] <= mid) cnt += L - 0 + 1;
}
return cnt;
}
inline int min_(int x,int y)
{
int posl = get(x), posr = get(y);
int ans = 2147483647;
if(posl == posr)
{
rep(i, x, y) ans = min(ans, a[i] + lz[get(i)]);
return ans;
}
rep(i, x, posl * len) ans = min(ans,a[i] + lz[get(i)]);
rep(i, posl + 1, posr - 1) ans = min(ans, v[i][0] + lz[i]);
rep(i, (posr - 1) * len + 1, y) ans = min(ans, a[i] + lz[get(i)]);
return ans;
}
inline int max_(int x,int y)
{
int posl = get(x), posr = get(y);
int ans = -2147483647;
if(posl == posr)
{
rep(i, x, y) ans = max(ans, a[i] + lz[get(i)]);
return ans;
}
rep(i, x, posl * len) ans = max(ans,a[i] + lz[get(i)]);
rep(i, posl + 1, posr - 1) ans = max(ans, v[i][len - 1] + lz[i]);
rep(i, (posr - 1) * len + 1, y) ans = max(ans, a[i] + lz[get(i)]);
return ans;
}
inline int qry(int l, int r, int k)
{
if(k > r - l + 1 or k < 1) return -1;
int posl = get(l), posr = get(r);
int ans = -1, L = min_(l, r), R = max_(l, r);
while(L <= R)
{
int mid = (L + R) >> 1;
if(check(l, r, mid) < k) L = mid + 1;
else ans = mid, R = mid - 1;
}
return ans;
}
signed main()
{
scanf("%lld %lld", &n, &m);
tot = ceil(n * 1.0 / len);
rep(i, 1, n)
{
scanf("%lld", &a[i]);
v[get(i)].push_back(a[i]);
}
rep(i, 1, tot) sort(v[i].begin(), v[i].end());
rep(i, 1, m)
{
scanf("%lld %lld %lld %lld", &opt, &l, &r, &k);
if(opt == 1)
printf("%lld\n", qry(l, r, k));
else updt(l, r, k);
}
return 0;
}
预处理和考虑影响
一、P4168 [Violet]蒲公英
给定长为 n n n 的序列和 m m m 次询问,每次询问区间 [ l , r ] [l,r] [l,r] 中最小的众数。
思路
众数:出现次数最多的数。
-
预处理
为了方便后面的计算,这里要处理两个数组:
-
定义 P i , j P_{i,j} Pi,j 表示第 i \text{i} i 到 j \text{j} j 个块中最小的众数;
-
定义 S i , j S_{i,j} Si,j 表示前 i \text{i} i (含第 i \text{i} i)个块中 j \text{j} j 这个数的出现次数(类似前缀和)。
-
-
查询
分两种情况讨论。
-
l \text{l} l 和 r \text{r} r 分别所在的块相邻或相同:直接暴力遍历、统计即可。
-
l \text{l} l 和 r \text{r} r 分别所在的块不相邻:
-
整块:
对于整块中的数的答案就是 P p o s l + 1 , p o s r − 1 P_{posl+1,posr-1} Pposl+1,posr−1 + 在散块中的出现次数( posl \text{posl} posl表示 l \text{l} l 所在的块, posr \text{posr} posr 同理)。
-
散块:
设一散块中的数为 $\text{x}$。那么它出现的次数为:在区间 $[l,posl* len]$ 中出现的次数 + 在整块 $[posl + 1,posr-1]$ 中出现的次数 + 在区间 $[(posr-1)* len + 1,r]$ 中出现的次数。 也就是:在区间 $[l,posl* len]$ 中出现的次数 + $S_{posr-1,x}-S_{posl,x}$ + 在区间 $[(posr-1)* len + 1,r]$ 中出现的次数。 第一项和第三项可以暴力统计,这样就可以得到答案了,再比较取最大值。
-
-
注意要先离散化数列中的值。
代码
#include<bits/stdc++.h>
using namespace std;
#define maxn 220
#define maxm 40040
#define rep(i, a, b) for(int i = a; i <= b; ++i)
int n, m, lst;
int s[maxn][maxm];
int pre[maxm], tmp[maxm];
struct node{
int org, al, id;
}a[maxm];
int len, tot, vis[maxm];
struct node2{
int num, tim;
}mxx, p[maxn][maxn], ans;
int l, r, posl, posr;
inline bool cmp1(node x, node y)
{
return x.org < y.org;
}
inline bool cmp2(node x, node y)
{
return x.id < y.id;
}
inline int getpos(int x)
{
int res = x / len;
if(x % len) return res + 1;
return res;
}
int main()
{
scanf("%d %d", &n, &m);
len = sqrt(n), tot = (n - 1) / len + 1;
rep(i, 1, n)
scanf("%d", &a[i].org), a[i].id = i;
sort(a + 1, a + n + 1, cmp1);
a[0].org = -1;
rep(i, 1, n)
{
a[i].al = a[i - 1].al;
if(a[i].org != a[i - 1].org) a[i].al += 1;
pre[a[i].al] = a[i].org;
}
sort(a + 1, a + n + 1, cmp2);
//输入+离散化
rep(i, 1, tot)
{
mxx.tim = 0, mxx.num = 1e9 +5;
memset(tmp, 0, sizeof tmp);
rep(j, i, tot)
{
rep(k, (j - 1) * len + 1, min(n, j * len))
{
tmp[a[k].al] += 1;
if(tmp[a[k].al] > mxx.tim)
mxx.tim = tmp[a[k].al], mxx.num = a[k].al;
else if(tmp[a[k].al] == mxx.tim)
mxx.num = min(mxx.num, a[k].al);
}
p[i][j] = mxx;
}
}
memset(tmp, 0, sizeof tmp);
rep(i, 1, tot)
{
rep(j, 1, n) s[i][a[j].al] = s[i - 1][a[j].al];
rep(j, (i - 1) * len + 1, min(n, i * len))
s[i][a[j].al] += 1;
}
//预处理两个数组
rep(i, 1, m)
{
scanf("%d %d", &l, &r);
l = ((l + lst - 1) % n) + 1, r = ((r + lst - 1) % n) + 1;
if(l > r) swap(l, r);
posl = getpos(l), posr = getpos(r);
//分情况讨论:
if(posr - posl <= 2)
{
mxx.num = 1e9 + 5, mxx.tim = 0;
memset(tmp, 0, sizeof tmp);
rep(j, l, r)
{
tmp[a[j].al] += 1;
if(tmp[a[j].al] > mxx.tim)
mxx.tim = tmp[a[j].al], mxx.num = a[j].al;
else if(tmp[a[j].al] == mxx.tim)
mxx.num = min(mxx.num, a[j].al);
}
printf("%d\n", lst = pre[mxx.num]);
}
else
{
mxx.num = 1e9 + 5, mxx.tim = 0;
memset(tmp, 0, sizeof tmp);
memset(vis, 0, sizeof vis);
ans = p[posl + 1][posr - 1];
rep(j, l, min(n, posl * len))
tmp[a[j].al] += 1;
rep(j, (posr - 1) * len + 1, r)
tmp[a[j].al] += 1;
rep(j, l, min(n, posl * len))
{
if(!vis[a[j].al])
{
vis[a[j].al] = 1;
int tme = tmp[a[j].al] + s[posr - 1][a[j].al] - s[posl][a[j].al];
if(tme > mxx.tim)
mxx.tim = tme, mxx.num = a[j].al;
else if(tme == mxx.tim)
mxx.num = min(mxx.num, a[j].al);
}
}
rep(j, (posr - 1) * len + 1, r)
{
if(!vis[a[j].al])
{
vis[a[j].al] = 1;
int tme = tmp[a[j].al] + s[posr - 1][a[j].al] - s[posl][a[j].al];
if(tme > mxx.tim)
mxx.tim = tme, mxx.num = a[j].al;
else if(tme == mxx.tim)
mxx.num = min(mxx.num, a[j].al);
}
}
if(mxx.tim > ans.tim + tmp[ans.num]) ans.num = mxx.num;
else if(mxx.tim == ans.tim + tmp[ans.num]) ans.num = min(ans.num, mxx.num);
printf("%d\n", lst = pre[ans.num]);
}
}
return 0;
}
二、P4135 作诗
给定长为 n n n 的数列,每次给定区间范围 [ l , r ] [l,r] [l,r],求其中出现了正偶数次的数的个数。
强制算法在线。
思路
和蒲公英很像,和后者相比这题很良心了。
预处理:类似,不同之处在于要在 O(n sprtn) \text{O(n sprtn)} O(n sprtn) 内处理完。因此做出一些更改:
-
定义 P i , j P_{i,j} Pi,j 表示第 i i i 到第 j j j 个块中出现正偶数次的数的数量;
-
定义 S i , j S_{i,j} Si,j 表示从第 i i i 个块的块首到 n n n 中出现了正偶数次的数的数量。
查询也比较简单:
-
区间范围小:暴力。
-
区间范围大:统计散块中的数的出现次数,再和整块中其出现次数比较奇偶、统计即可。
代码
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
using namespace std;
#define maxn 100005
#define maxm 505
#define rep(i, a, b) for(int i = a; i <= b; ++i)
int n, c, m, l, r, lst;
int len, tot;
int a[maxn], p[maxm][maxm], s[maxm][maxn];
int b[maxn], vis[maxn];
int st[maxn], top;
inline int get(int x)
{
int res = x / len;
if(x % len) res += 1;
return res;
}
inline void pre()
{
rep(i, 1, tot)
{
int tmp = 0;
rep(j, (i - 1) * len + 1, n)
{
if(s[i][a[j]] & 1) tmp += 1;
else if(s[i][a[j]] and !(s[i][a[j]] & 1)) tmp -= 1;
s[i][a[j]] += 1;
if(!(j % len) or j == n) p[i][get(j)] = tmp;
}
}
}
int main()
{
scanf("%d %d %d", &n, &c, &m);
len = sqrt(n), tot = (n - 1) / len + 1;
rep(i, 1, n) scanf("%d", &a[i]);
pre();
rep(i, 1, m)
{
scanf("%d %d", &l, &r);
l = (l + lst) % n + 1, r = (r + lst) % n + 1;
if(l > r) swap(l, r);
int posl = get(l), posr = get(r);
if(posr - posl <= 1)
{
int tmp = 0;
rep(j, l, r)
{
if(b[a[j]] & 1) tmp += 1;
else if(b[a[j]] and !(b[a[j]] & 1)) tmp -= 1;
b[a[j]] += 1;
}
memset(b, 0, sizeof b);
printf("%d\n", lst = tmp);
}
else
{
int ans;
if(posl + 1 <= posr - 1) ans = p[posl + 1][posr - 1];
rep(j, l, posl * len)
{
b[a[j]] += 1;
st[++top] = a[j];
}
rep(j, (posr - 1) * len + 1, r)
{
b[a[j]] += 1;
st[++top] = a[j];
}
while(top)
{
int t = st[top];
if(b[t] != 0)
{
int tim = s[posl + 1][t] - s[posr][t];
if((tim > 0) and (tim & 1) == 0 and (b[t] & 1)) ans--;
else if(((tim > 0) and (tim & 1)) and (b[t] & 1)) ans++;
else if((tim == 0) and (b[t] & 1) == 0) ans++;
b[t] = 0;
}
top--;
}
printf("%d\n", lst = ans);
}
}
return 0;
}

浙公网安备 33010602011771号