「分块」LibreOJ 数列分块入门 1 ~ ⑨
\(\text{LibreOJ数列分块入门}\) \(1 \sim \text{⑨}\)
题目汇总
T1: 区间加, 单点查询:
直接暴力分块
完整块 修改永久懒标记
两端不完整块暴力修改元素值
单点查询值 = 元素值 + 懒标记
完整块数量不超过 \(\sqrt n\), 两不完整块总长度不超过 \(2 \sqrt n\)
总复杂度\(O(n \sqrt{n})\)
//知识点:分块
/*
By:Luckyblock
*/
#include <cstdio>
#include <cctype>
#include <cmath>
#define ll long long
const int MARX = 5e4 + 10;
//===========================================================
//Belong:元素所在块的编号; Fir, Las:第i个块的左右边界
int N, lazyNum, Belong[MARX], Fir[MARX], Las[MARX];
ll Number[MARX], lazy[MARX]; //Number:元素值; lazy:第i个块的永久懒标记
//===========================================================
inline int read()
{
int w = 0, f = 1; char ch = getchar();
for(; !isdigit(ch); ch = getchar()) if(ch == '-') f = -1;
for(; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
return f * w;
}
void Prepare() //预处理
{
N = read();
for(int i = 1; i <= N; i ++) Number[i] = (ll) read();
lazyNum = sqrt(N);
for(int i = 1; i <= lazyNum; i ++) //进行分块
Fir[i] = (i - 1) * lazyNum + 1,
Las[i] = i * lazyNum;
//处理数列尾的 不完整块
if(Las[lazyNum] < N) lazyNum ++, Fir[lazyNum] = Las[lazyNum - 1] + 1, Las[lazyNum] = N;
for(int i = 1; i <= lazyNum; i ++) //处理每个元素 所在块的编号
for(int j = Fir[i]; j <= Las[i]; j ++)
Belong[j] = i;
}
void Change(int L, int R, ll Val) //区间加
{
if(Belong[L] == Belong[R]) //当修改区间 被一个块包含 (不完整块
{
for(int i = L; i <= R; i ++) Number[i] += Val; //直接暴力修改
return ;
}
for(int i = Belong[L] + 1; i <= Belong[R] - 1; i ++) lazy[i] += Val; //修改完整块
for(int i = L; i <= Las[Belong[L]]; i ++) Number[i] += Val; //修改左端不完整块
for(int i = Fir[Belong[R]]; i <= R; i ++) Number[i] += Val; //修改右端不完整块
}
//===========================================================
int main()
{
Prepare();
for(int i = 1; i <= N; i ++)
{
int opt = read(), L = read(), R = read(), Val = read();
if(! opt) Change(L, R, (ll) Val);
else printf("%lld\n", Number[R] + lazy[Belong[R]]); //单点查询
}
return 0;
}
T2: 区间加, 区间查询小于给定值 元素数:
同 1, 先进行分块, 再分别考虑完整块与不完整块 :
-
先不考虑 修改操作 :
不完整块 可直接暴力查询
对于完整块, 查询小于给定值元素数, 易联想到 lower_bound 操作
由于分块后单个块较小, 则可直接暴力排序, 再通过 lower_bound 求得元素数 -
再考虑 修改操作
同样, 不完整块 可直接进行暴力修改
对于完整块, 区间加后, 不影响它们之间的排名, 排序后 顺序不变
进行区间加 x 后 再进行区间查询 y , 与 在区间加 x 之前, 区间查询 y - x , 得到的结果相同
则可使用类似 1 的懒惰标记法 进行区间修改, 并根据懒标记调整查询的值
由上, 则需记录 未排序的数列 与 排序后的数列(需要进行 lower_bound)
可使用 vector 类, 来储存每一排序后的完整块
-
修改时 :
不完整块 暴力修改未排序数列, 将 原排序后的数列 重新赋值并排序 (为了便于 查询)
其总长度不超过 \(2\sqrt n\) , 单次排序复杂度 \(O(\sqrt n\log \sqrt n)\)完整块直接更新 懒标记即可, 其个数 不超过 \(\sqrt n\)
则单次修改总复杂度为 \(O(\sqrt n + \sqrt n\log \sqrt n)\)
-
查询时 :
不完整块 暴力查询未排序数列, 其总长度不超过 \(2\sqrt n\)
完整块 对排序后数列进行 lower_bound,
其个数 不超过 \(\sqrt n\), 单次 lower_bound 复杂度为 \(O(\sqrt n\log \sqrt n)\)则单次修改总复杂度也为 \(O(\sqrt n + \sqrt n\log \sqrt n)\)
在预处理分块时 需要对整个数列进行排序预处理, 复杂度 \(O(n \log n)\)
则上述算法 总复杂度为 \(O(n\log n + n\sqrt n\log\sqrt n)\)
//知识点:分块
/*
By:Luckyblock
*/
#include <cstdio>
#include <cctype>
#include <cmath>
#include <vector>
#include <algorithm>
#define ll long long
const int MARX = 5e4 + 10;
//===========================================================
int N, original[MARX];
int BlockNum, Belong[MARX], Fir[MARX], Las[MARX], lazy[MARX];
std :: vector <int> block[510]; //存 排序之后的每个块
//===========================================================
inline int read()
{
int w = 0, f = 1; char ch = getchar();
for(; !isdigit(ch); ch = getchar()) if(ch == '-') f = -1;
for(; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
return f * w;
}
void Reset(int pos) //为第pos个块 重新赋值并排序
{
block[pos].clear(); //清空
for(int i = Fir[pos]; i <= Las[pos]; i ++) block[pos].push_back(original[i]); //
std :: sort(block[pos].begin(), block[pos].end()); //重排序
}
void Prepare() //预处理分块
{
N = read();
for(int i = 1; i <= N; i ++) original[i] = read();
BlockNum = sqrt(N);
for(int i = 1; i <= BlockNum; i ++)
Fir[i] = (i - 1) * BlockNum + 1, //
Las[i] = i * BlockNum;
if(Las[BlockNum] < N) BlockNum ++, Fir[BlockNum] = Las[BlockNum - 1] + 1, Las[BlockNum] = N;
for(int i = 1; i <= BlockNum; i ++)
for(int j = Fir[i]; j <= Las[i]; j ++)
Belong[j] = i;
for(int i = 1; i <= BlockNum; i ++) Reset(i); //数列排序 初始化
}
void Change(int L, int R, int Val) //区间加操作
{
int Bell = Belong[L], Belr = Belong[R];
if(Bell == Belr) //修改不完整块
{
for(int i = L; i <= R; i ++) original[i] += Val;
Reset(Bell); //更新排序后数列
return ;
}
for(int i = Bell + 1; i <= Belr - 1; i ++) lazy[i] += Val; //修改完整块
for(int i = L; i <= Las[Bell]; i ++) original[i] += Val; //修改不完整块
Reset(Bell);
for(int i = Fir[Belr]; i <= R; i ++) original[i] += Val; //修改不完整块
Reset(Belr);
}
int Query(int L, int R, int Val) //区间查询小于给定值 元素数
{
int Bell = Belong[L], Belr = Belong[R], ret = 0;
if(Bell == Belr) //不完整块
{
for(int i = L; i <= R; i ++) ret += (original[i] + lazy[Bell] < Val); ///暴力查询
return ret;
}
for(int i = L; i <= Las[Bell]; i ++) ret += (original[i] + lazy[Bell] < Val); //不完整块
for(int i = Fir[Belr]; i <= R; i ++) ret += (original[i] + lazy[Belr] < Val); //不完整块
for(int i = Bell + 1; i <= Belr - 1; i ++) ret += lower_bound(block[i].begin(), block[i].end(), Val - lazy[i]) - block[i].begin(); ///完整块 二分
return ret;
}
//===========================================================
int main()
{
Prepare();
for(int i = 1; i <= N; i ++)
{
int opt = read(), L = read(), R = read(), Val = read();
if(! opt) Change(L, R, Val);
else printf("%d\n", Query(L, R, Val * Val));
}
return 0;
}
T3: 区间加, 区间查询小于给定值 最大元素:
-
算法1 :
套用 数列分块2 的做法, 在进行lower_bound时 求得每个块中 小于给定值 最大元素,
再将多个块的答案合并 取最大值.
总复杂度 \(O(n\log n + n\sqrt n\log\sqrt n)\). -
算法2 :
查询前驱 ?来发Splay
可以对每个块 都维护一个 set , 来代替算法 1 中的排序数组.
重赋值和插入操作 都会变得更方便 .但出题人的写法有些问题
重赋值时 擦除操作 会擦除 插入的新元素
将 std 中的 set 改为 multiset即可
//知识点:分块
/*
By:Luckyblock
*/
#include <cstdio>
#include <cctype>
#include <cmath>
#include <set>
#include <algorithm>
#define ll long long
const int MARX = 1e5 + 10;
//===========================================================
int N, original[MARX];
int BlockNum, Belong[MARX], Fir[MARX], Las[MARX], lazy[MARX];
std :: multiset <int> block[1010];
//===========================================================
inline int read()
{
int w = 0, f = 1; char ch = getchar();
for(; !isdigit(ch); ch = getchar()) if(ch == '-') f = -1;
for(; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
return f * w;
}
void Prepare() //预处理
{
N = read();
for(int i = 1; i <= N; i ++) original[i] = read();
BlockNum = sqrt(N);
for(int i = 1; i <= BlockNum; i ++)
Fir[i] = (i - 1) * BlockNum + 1, //
Las[i] = i * BlockNum;
if(Las[BlockNum] < N) BlockNum ++, Fir[BlockNum] = Las[BlockNum - 1] + 1, Las[BlockNum] = N;
for(int i = 1; i <= BlockNum; i ++)
for(int j = Fir[i]; j <= Las[i]; j ++)
Belong[j] = i, block[i].insert(original[j]); //插入set
}
void Change(int L, int R, int Val) //修改
{
int Bell = Belong[L], Belr = Belong[R];
if(Bell == Belr) //不完整块
{
for(int i = L; i <= R; i ++) //将l ~ r全部加入set
{
block[Bell].erase(original[i]); //擦除原有元素
original[i] += Val;
block[Bell].insert(original[i]); //插入新元素
}
return ;
}
for(int i = Bell + 1; i <= Belr - 1; i ++) lazy[i] += Val;
for(int i = L; i <= Las[Bell]; i ++) //不完整块
{
block[Bell].erase(original[i]); //擦除原有元素
original[i] += Val;
block[Bell].insert(original[i]); //插入新元素
}
for(int i = Fir[Belr]; i <= R; i ++)
{
block[Belr].erase(original[i]);
original[i] += Val;
block[Belr].insert(original[i]);
}
}
int Query(int L, int R, int Val) //查询
{
int Bell = Belong[L], Belr = Belong[R], ret = - 1;
if(Bell == Belr) //不完整块
{
for(int i = L; i <= R; i ++) //暴力查询
if(original[i] + lazy[Bell] < Val)
ret = std :: max(ret, original[i] + lazy[Bell]);
return ret;
}
for(int i = L; i <= Las[Bell]; i ++) //不完整块
if(original[i] + lazy[Bell] < Val) //暴力查询
ret = std :: max(ret, original[i] + lazy[Bell]);
for(int i = Fir[Belr]; i <= R; i ++) //不完整块
if(original[i] + lazy[Belr] < Val) //暴力查询
ret = std :: max(ret, original[i] + lazy[Belr]);
for(int i = Bell + 1; i <= Belr - 1; i ++) //完整块
{
int value = Val - lazy[i];
std :: set <int> :: iterator it = block[i].lower_bound(value); //查询前驱
if(it == block[i].begin()) continue; //无前驱
ret = std :: max(ret, *(-- it) + lazy[i]);
}
return ret;
}
//===========================================================
int main()
{
Prepare();
for(int i = 1; i <= N; i ++)
{
int opt = read(), L = read(), R = read(), Val = read();
if(! opt) Change(L, R, Val);
else printf("%d\n", Query(L, R, Val));
}
return 0;
}
T4: 区间加, 区间求和:
在 T1 基础上, 对每一完整块 维护其元素和.
修改时:
- 对于不完整块, 暴力修改数列, 并更新 元素和.
- 对于完整块, 使用懒标记修改,
为防止爆long long, 不直接更新 元素和.
查询时:
- 不完整块 暴力还原并统计贡献.
- 完整块贡献 = 记录的元素和 + 块大小 * 懒标记 .
单次修改查询时 完整块数量不超过 \(\sqrt n\), 两不完整块总长度不超过 \(2 \sqrt n\).
总复杂度\(O(n \sqrt{n})\).
//知识点:分块
/*
By:Luckyblock
*/
#include <cstdio>
#include <cctype>
#include <cmath>
#define ll long long
const int MARX = 5e4 + 10;
//===========================================================
int N, BlockNum, Belong[MARX], Fir[MARX], Las[MARX];
ll Number[MARX], sum[MARX], lazy[MARX]; //sum:元素和
//===========================================================
inline int read()
{
int w = 0, f = 1; char ch = getchar();
for(; !isdigit(ch); ch = getchar()) if(ch == '-') f = -1;
for(; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
return f * w;
}
void Prepare() //预处理 分块
{
N = read();
for(int i = 1; i <= N; i ++) Number[i] = (ll) read();
BlockNum = sqrt(N);
for(int i = 1; i <= BlockNum; i ++)
Fir[i] = (i - 1) * BlockNum + 1,
Las[i] = i * BlockNum;
if(Las[BlockNum] < N) BlockNum ++, Fir[BlockNum] = Las[BlockNum - 1] + 1, Las[BlockNum] = N;
for(int i = 1; i <= BlockNum; i ++)
for(int j = Fir[i]; j <= Las[i]; j ++)
Belong[j] = i, sum[i] += Number[j];
}
void Change(int L, int R, ll Val) //区间加
{
int Bell = Belong[L], Belr = Belong[R];
if(Bell == Belr) //不完整块
{
for(int i = L; i <= R; i ++) Number[i] += Val, sum[Bell] += Val; //修改数列, 并修改元素和
return ;
}
for(int i = Bell + 1; i <= Belr - 1; i ++) lazy[i] += Val; //完整块 修改懒标记
for(int i = L; i <= Las[Bell]; i ++) Number[i] += Val, sum[Bell] += Val;
for(int i = Fir[Belr]; i <= R; i ++) Number[i] += Val, sum[Belr] += Val;
}
ll Query(int L, int R, ll mod) //区间查询
{
int Bell = Belong[L], Belr = Belong[R], ret = 0;
if(Bell == Belr) //不完整块
{
for(int i = L; i <= R; i ++) ret = ((ret + Number[i]) % mod + lazy[Bell]) % mod; //暴力查询
return ret;
}
for(int i = Bell + 1; i <= Belr - 1; i ++) ret = ((ret + (Las[i] - Fir[i] + 1) * lazy[i] % mod) + sum[i]) % mod; //完整块
for(int i = L; i <= Las[Bell]; i ++) ret = ((ret + Number[i]) % mod + lazy[Bell]) % mod;
for(int i = Fir[Belr]; i <= R; i ++) ret = ((ret + Number[i]) % mod + lazy[Belr]) % mod;
return ret;
}
//===========================================================
int main()
{
Prepare();
for(int i = 1; i <= N; i ++)
{
int opt = read(), L = read(), R = read(), Val = read();
if(! opt) Change(L, R, (ll) Val);
else printf("%lld\n", Query(L, R, (ll) Val + 1));
}
return 0;
}
T5: 区间开方, 区间求和:
本题最大坑点 : 数列中可能有 0 .
对于求和操作 :
同 T4 , 维护完整块元素和.
不完整块暴力查询, 完整块查询 整块元素和.
显然, 单次求和复杂度为 \(O(\sqrt{n})\)
对于修改操作 :
-
不完整块 直接暴力开方 .
-
对于完整块 :
由于开方运算不满足 一系列运算律, 无法使用标记法.怎么办? 考虑直接暴力.
众所周知, \(\Bigg\lfloor\sqrt{\sqrt{\sqrt{\sqrt{\sqrt {2 ^{31}}}}}}\Bigg\rfloor = 1\) .
对于一个数, 最多 5 次修改后 必然等于 1 或 0 .
对于一个块, 最多 5 次修改后 必然全部由 1 或 0 组成 .
而 \(\sqrt{1} = 1,\ \sqrt{0} = 0\) , 修改操作对其不会有影响 .则可以标记一个完整块是否全为 \(1/0\).
若全为 \(1/0\), 则无论其修改次数, 元素和都不会改变, 不进行修改操作.
否则 直接暴力修改, 并在暴力修改时 完成对标记的更新.由于每个完整块的修改次数 \(\le 5\), 而不完整块的大小 \(\le \sqrt{n}\).
则所有修改操作 总复杂度最大为 \(O(5n + n\sqrt{n})\).
//知识点:分块
/*
By:Luckyblock
数列中可能有 0
*/
#include <cstdio>
#include <cctype>
#include <algorithm>
#include <cmath>
#define int long long
const int MARX = 5e4 + 10;
//===========================================================
int N, BlockNum, Fir[MARX], Las[MARX], Belong[MARX];
int Number[MARX], sum[MARX], flag[MARX]; //flag:标记第i块 是否全为 1/0
//===========================================================
inline int read()
{
int w = 0, f = 1; char ch = getchar();
for(; !isdigit(ch); ch = getchar()) if(ch == '-') f = -1;
for(; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
return f * w;
}
void Prepare() //预处理分块
{
N = read();
for(int i = 1; i <= N; i ++) Number[i] = read();
BlockNum = sqrt(N);
for(int i = 1; i <= BlockNum; i ++)
Fir[i] = (i - 1) * BlockNum + 1,
Las[i] = i * BlockNum;
if(Las[BlockNum] < N) BlockNum ++, Fir[BlockNum] = Las[BlockNum - 1] + 1, Las[BlockNum] = N;
for(int i = 1; i <= BlockNum; i ++)
for(int j = Fir[i]; j <= Las[i]; j ++)
Belong[j] = i, sum[i] += Number[j];
}
void Sqrt(int pos) //整块开方操作
{
if(flag[pos]) return ;
flag[pos] = 1, sum[pos] = 0; //更新
for(int i = Fir[pos]; i <= Las[pos]; i ++) //枚举各元素
{
Number[i] = sqrt(Number[i]), sum[pos] += Number[i]; //开方并求和
if(Number[i] > 1) flag[pos] = 0; //出现了 非1/0 的元素
}
}
void Change(int L, int R) //区间开方
{
int Bell = Belong[L], Belr = Belong[R];
if(Bell == Belr) //不完整块
{
if(! flag[Bell]) for(int i = L; i <= R; i ++) //暴力开方, 并求和
{
sum[Bell] -= Number[i];
Number[i] = sqrt(Number[i]);
sum[Bell] += Number[i];
}
return ;
}
for(int i = Bell + 1; i <= Belr - 1; i ++) Sqrt(i); //完整块
if(! flag[Bell]) for(int i = L; i <= Las[Bell]; i ++) //不完整块
{
sum[Bell] -= Number[i];
Number[i] = sqrt(Number[i]);
sum[Bell] += Number[i];
}
if(! flag[Belr]) for(int i = Fir[Belr]; i <= R; i ++) //不完整块
{
sum[Belr] -= Number[i];
Number[i] = sqrt(Number[i]);
sum[Belr] += Number[i];
}
}
int Query(int L, int R) //区间和
{
int Bell = Belong[L], Belr = Belong[R], ret = 0;
if(Bell == Belr)
{
for(int i = L; i <= R; i ++) ret += Number[i];
return ret;
}
for(int i = Bell + 1; i <= Belr - 1; i ++) ret += sum[i];
for(int i = L; i <= Las[Bell]; i ++) ret += Number[i];
for(int i = Fir[Belr]; i <= R; i ++) ret += Number[i];
return ret;
}
//===========================================================
signed main()
{
Prepare();
for(int i = 1; i <= N; i ++)
{
int opt = read(), L = read(), R = read(), Val = read();
if(! opt) Change(L, R);
else printf("%lld\n", Query(L, R));
}
return 0;
}
T6: 单点插入,单点查询:
由于是随机数据 , 先想到 用 vector 的 insert 操作骗分.
骗分之余思考上述算法 能不能用分块优化 :
众所周知 , vector 的 insert 操作, 复杂度与 vector 内元素个数 呈正比.
则可使用分块 来构建多个vector , 以降低插入复杂度 :
插入时 枚举所有块, 将元素插入指定位置所在块中, 单次复杂度近似 \(O(\sqrt n)\).
查询时 枚举所有块, 在指定位置所在块中查询 , 单次复杂度近似 \(O(\sqrt n)\).
总复杂度近似 \(O(n\sqrt n)\).
然后就有了一份倒数第一快 但是 能过的代码 :
//知识点:分块
/*
By:attack204
*/
#include <cstdio>
#include <cctype>
#include <vector>
#include <cmath>
#define ll long long
const int MARX = 1e5 + 10;
//===========================================================
int N, BlockNum;
std :: vector <int> block[MARX];
//===========================================================
inline int read()
{
int w = 0, f = 1; char ch = getchar();
for(; !isdigit(ch); ch = getchar()) if(ch == '-') f = -1;
for(; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
return f * w;
}
void Prepare()
{
N = read(); BlockNum = sqrt(N);
for(int i = 1, tmp; i <= N; i ++)
tmp = read(),
block[(i - 1) / BlockNum + 1].push_back(tmp);
BlockNum = (N - 1) / BlockNum + 1;
}
void Insert(int Pos, int Val)
{
for(int i = 1; i <= BlockNum; i ++)
if(Pos <= block[i].size()) {block[i].insert(block[i].begin() + Pos - 1, Val); return ;}
else Pos -= block[i].size();
}
int Query(int Pos)
{
for(int i = 1; i <= BlockNum; i ++)
if(Pos <= block[i].size()) return block[i][Pos - 1];
else Pos -= block[i].size();
}
//===========================================================
int main()
{
Prepare();
for(int i = 1; i <= N; i ++)
{
int opt = read(), L = read(), R = read(), Val = read();
if(! opt) Insert(L, R);
else printf("%d\n", Query(R));
}
return 0;
}
上述复杂度分析 都是基于 随机数据 的前提下的.
如果出题人恶意构造类似下方的数据 :
100000
1 1 ... 1
1 1 1 1
1 1 1 1
...
上述代码会出现 不断插入同一块的情况 ,
单次插入会被卡到 \(O(n)\) , 总复杂度被卡到\(O(n ^ 2)\).
考虑 如何才能使各块的大小相对平均, 避免产生上述问题.
直接再分一遍块不就行了 !
为块的大小设定上限 , 在插入同时判断块的大小是否达到上限.
若达到上限, 枚举所有元素, 重新进行分块, 以平均块的大小.
//知识点:分块
/*
By:Luckyblock
*/
#include <cstdio>
#include <cctype>
#include <vector>
#include <cmath>
#define ll long long
const int MARX = 1e5 + 10;
//===========================================================
int N, OriginalBlockSize, BlockNum;
std :: vector <int> block[MARX], tmp;
//===========================================================
inline int read()
{
int w = 0, f = 1; char ch = getchar();
for(; !isdigit(ch); ch = getchar()) if(ch == '-') f = -1;
for(; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
return f * w;
}
void Prepare() //首次分块
{
N = read(); OriginalBlockSize = sqrt(N);
for(int i = 1, re; i <= N; i ++)
re = read(),
block[(i - 1) / OriginalBlockSize + 1].push_back(re);
BlockNum = (N - 1) / OriginalBlockSize + 1;
}
void Rebuild() //重新分块
{
tmp.clear();
for(int i = 1; i <= BlockNum; i ++) //枚举所有元素
{
std :: vector <int> :: iterator it; //
for(it = block[i].begin(); it != block[i].end(); it ++) tmp.push_back(*it); //放入tmp中
block[i].clear();
}
int BlockSize = sqrt(tmp.size()); //新的块大小
for(int i = 0; i < tmp.size(); i ++) block[(i - 1) / BlockSize + 1].push_back(tmp[i]); //重新分块
BlockNum = (tmp.size() - 1) / BlockSize + 1;
}
void Insert(int Pos, int Val) //插入操作
{
int Pos1 = 1, Pos2 = Pos; //找到所在快 Pos1 与 块内位置 Pos2
while(Pos2 > block[Pos1].size()) Pos2 -= block[Pos1].size(), Pos1 ++;
Pos2 --; //vector 下标以 0开始
block[Pos1].insert(block[Pos1].begin() + Pos2, Val); //插入
if(block[Pos1].size() > 10 * OriginalBlockSize) Rebuild(); //单个块超过限制大小
}
int Query(int Pos) //查询操作
{
int Pos1 = 1, Pos2 = Pos; //找到所在快 Pos1 与 块内位置 Pos2
while(Pos2 > block[Pos1].size()) Pos2 -= block[Pos1].size(), Pos1 ++;
return block[Pos1][Pos2 - 1]; //vector 下标以 0开始
}
//===========================================================
int main()
{
Prepare();
for(int i = 1; i <= N; i ++)
{
int opt = read(), L = read(), R = read(), Val = read();
if(! opt) Insert(L, R);
else printf("%d\n", Query(R));
}
return 0;
}
T7: 区间加, 区间乘, 单点查询:
如果只有区间乘操作, 只需要将 T1 的 + 改为 * 即可.
但是 区间加区间乘 同时存在, 需记录 两懒标记, 就需要注意懒标记与 数列值之间的关系
(以下, 使用\(k\)代表乘法懒标记, \(b\)代表加法标记, \(x\)代表数列值).
对于完整块的两修改操作 :
-
当出现 先乘后加 时, 有 : \((kx + b) + y = kx + b + y\) .
若只修改 数列值 \(x\) , 则会出现: \([k(x + y) + b] = kx + ky + b \not= kx + b + y\) . -
当出现 先加后乘 时, 有 : \((kx + b) \times y = kxy + by\) .
若只修改 数列值, 则会出现: \(kxy + b \not= kxy + by\) .
若只修改 乘法懒标记, 则会出现: \(kyx + b \not= kxy + by\) . -
进行修改操作时 要进行多方面的考虑, 需要进行分类讨论
吗, 当然是不需要的.不完整块较小 , 可以直接暴力处理 .
先通过修改前的懒标记 求得整个块各元素值 , 并清空懒标记,
再暴力修改数列值 , 即可避免上述情况发生.
对于完整块的两修改操作 :
- 区间加时, 直接修改加法懒标记即可.
- 区间乘时, 根据 乘法结合律有 :\((kx + b) \times y = kxy + by\).
则将 加法标记 与 乘法标记同乘即可.
单点查询, 即输出 \(kx + b\), 复杂度 \(O(1)\).
完整块数量不超过 \(\sqrt n\), 两不完整块总长度不超过 \(2 \sqrt n\)
综上, 总复杂度为 \(O(n\sqrt n)\)
//知识点:分块
/*
By:Luckyblock
注意标记下传
*/
#include <cstdio>
#include <cctype>
#include <cmath>
#define ll long long
const int MARX = 1e5 + 10;
const int mod = 10007;
//===========================================================
int N, BlockNum, Belong[MARX], Fir[MARX], Las[MARX];
ll Number[MARX], prod_lazy[MARX], plus_lazy[MARX];
//===========================================================
inline int read()
{
int w = 0, f = 1; char ch = getchar();
for(; !isdigit(ch); ch = getchar()) if(ch == '-') f = -1;
for(; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
return f * w;
}
void Reset(int Pos) //暴力还原数列
{
for(int i = Fir[Pos]; i <= Las[Pos]; i ++)
Number[i] = (Number[i] * prod_lazy[Pos] % mod + plus_lazy[Pos]) % mod;
plus_lazy[Pos] = 0, prod_lazy[Pos] = 1; //清空标记
}
void Prepare() //预处理分块
{
N = read();
for(int i = 1; i <= N; i ++) Number[i] = (ll) read();
BlockNum = sqrt(N);
for(int i = 1; i <= BlockNum; i ++)
Fir[i] = (i - 1) * BlockNum + 1,
Las[i] = i * BlockNum;
if(Las[BlockNum] < N) BlockNum ++, Fir[BlockNum] = Las[BlockNum - 1] + 1, Las[BlockNum] = N;
for(int i = 1; i <= BlockNum; i ++)
for(int j = Fir[i]; j <= Las[i]; j ++)
Belong[j] = i, prod_lazy[i] = 1ll;
}
void ChangePlus(int L, int R, ll Val) //区间加
{
int Bell = Belong[L], Belr = Belong[R];
if(Bell == Belr)
{
Reset(Bell);
for(int i = L; i <= R; i ++) Number[i] = (Number[i] + Val) % mod;
return ;
}
for(int i = Bell + 1; i <= Belr - 1; i ++) plus_lazy[i] = (plus_lazy[i] + Val) % mod;
Reset(Bell);
for(int i = L; i <= Las[Bell]; i ++) Number[i] = (Number[i] + Val) % mod;
Reset(Belr);
for(int i = Fir[Belr]; i <= R; i ++) Number[i] = (Number[i] + Val) % mod;
}
void ChangeProd(int L, int R, ll Val) //区间乘
{
int Bell = Belong[L], Belr = Belong[R];
if(Bell == Belr) //不完整块
{
Reset(Belong[L]);
for(int i = L; i <= R; i ++) Number[i] = (Number[i] * Val) % mod;
return ;
}
for(int i = Bell + 1; i <= Belr - 1; i ++) //完整块
prod_lazy[i] = (prod_lazy[i] * Val) % mod,
plus_lazy[i] = (plus_lazy[i] * Val) % mod;
Reset(Bell);
for(int i = L; i <= Las[Bell]; i ++) Number[i] = (Number[i] * Val) % mod;
Reset(Belr);
for(int i = Fir[Belr]; i <= R; i ++) Number[i] = (Number[i] * Val) % mod;
}
//===========================================================
int main()
{
Prepare();
for(int i = 1; i <= N; i ++)
{
int opt = read(), L = read(), R = read(), Val = read();
if(opt == 0) ChangePlus(L, R, (ll) Val);
if(opt == 1) ChangeProd(L, R, (ll) Val);
if(opt == 2) printf("%lld\n", (Number[R] * prod_lazy[Belong[R]] % mod + plus_lazy[Belong[R]])% mod);
}
return 0;
}
T8: 区间赋值, 查询区间等于给定值 元素数:
同T6 , 还是先想暴力.
对于修改操作, 不完整块 直接暴力修改 , 完整块打懒标记.
单次修改操作 , 复杂度显然为 \(O(\sqrt n)\) .
对于查询操作, 不完整块在暴力修改时 顺便暴力查询, 完整块分下列两种情况讨论 :
- 有懒标记 , 则块内元素相同 , 直接判断标记与查询值 是否相同.
- 无懒标记 , 没有什么好的处理方法 , 直接暴力查询.
单次查询操作, 处理不完整块复杂度为 \(O(\sqrt n)\) ,
而对于完整块的处理, 在无懒标记的情况下 可能会被卡到 \(O(n)\).
然后10min写完, 交了一发就A了....???!!!
稍加分析, 可以发现:
若使一次查询, 其完整块查询操作 复杂度变为\(O(n)\) , 需要使区间内完整块全部无懒标记.
而一次修改操作 , 只能使修改区间两端的 2不完整块 懒标记清空,
若使一区间完整块懒标记全部清空, 需要先经过 \(\sqrt n\) 次修改操作
最多只会有\(\sqrt n\) 次 \(\sqrt n\) 次的修改操作
故上述算法 总复杂度 近似\(O(n \sqrt n)\)
//知识点:分块
/*
By:Luckyblock
注意标记下传
*/
#include <cstdio>
#include <cctype>
#include <cmath>
#define ll long long
const int MARX = 1e5 + 10;
//===========================================================
int N, Number[MARX];
int BlockNum, Belong[MARX], Fir[MARX], Las[MARX];
int same[MARX]; //完整块的赋值标记
bool sameflag[MARX]; //标记 完整块内是否全部相同
//===========================================================
inline int read()
{
int w = 0, f = 1; char ch = getchar();
for(; !isdigit(ch); ch = getchar()) if(ch == '-') f = -1;
for(; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
return f * w;
}
void Prepare() //预处理分块
{
N = read(); BlockNum = sqrt(N);
for(int i = 1; i <= N; i ++) Number[i] = read();
for(int i = 1; i <= BlockNum; i ++)
Fir[i] = (i - 1) * BlockNum + 1,
Las[i] = i * BlockNum;
if(Las[BlockNum] < N) BlockNum ++, Fir[BlockNum] = Las[BlockNum - 1] + 1, Las[BlockNum] = N;
for(int i = 1; i <= BlockNum; i ++)
for(int j = Fir[i]; j <= Las[i]; j ++)
Belong[j] = i;
}
void Reset(int Pos) //区间赋值, 清空标记
{
if(! sameflag[Pos]) return ; //未标记
for(int i = Fir[Pos]; i <= Las[Pos]; i ++) Number[i] = same[Pos]; //区间赋值
sameflag[Pos] = 0; //清空标记
}
int Solve(int L, int R, int Val) //区间查询区间等于给定值 并 赋值
{
int Bell = Belong[L], Belr = Belong[R], ret = 0;
if(Bell == Belr) //不完整块
{
Reset(Bell);
for(int i = L; i <= R; i ++) ret += (Number[i] == Val), Number[i] = Val; //暴力判断并重赋值
return ret;
}
for(int i = Bell + 1; i <= Belr - 1; i ++) //完整块
{
if(sameflag[i]) ret += (Las[i] - Fir[i] + 1) * (same[i] == Val); //块内全部相同时
else for(int j = Fir[i]; j <= Las[i]; j ++) ret += (Number[j] == Val); //否则暴力判断
same[i] = Val, sameflag[i] = 1; //标记
}
Reset(Bell); //不完整块
for(int i = L; i <= Las[Bell]; i ++) ret += (Number[i] == Val), Number[i] = Val;
Reset(Belr); //不完整块
for(int i = Fir[Belr]; i <= R; i ++) ret += (Number[i] == Val), Number[i] = Val;
return ret;
}
//===========================================================
int main()
{
Prepare();
for(int i = 1; i <= N; i ++)
{
int L = read(), R = read(), Val = read();
printf("%d\n", Solve(L, R, Val));
}
return 0;
}
T9: 查询区间最小众数:
感觉自己没时间口胡了 , 详见 Luogu 题解 传送门
另, 实在不想写带log的算法了,
经过长达1h的卡常后, 以下代码的分数 依照评测机心情在 [96, 100] 之间浮动.
//知识点:分块
/*
By:Luckyblock
*/
#pragma GCC optimize(2)
#include <cstdio>
#include <cctype>
#include <cstring>
#include <cmath>
#include <algorithm>
#define Re register
#define ll long long
const int MARX = 1e5 + 10;
const int MAXBLOCKNUM = 510;
//===========================================================
struct Mapping
{
int Data, Rank;
} Tmp[MARX];
int N, M, MaxData, LastAns, Number[MARX], Original[MARX];
int BlockSize, BlockNum, Belong[MARX], Fir[MARX], Las[MARX];
int TmpCnt[MARX], Cnt[MAXBLOCKNUM][MARX], Mode[MAXBLOCKNUM][MAXBLOCKNUM];
//===========================================================
inline int read()
{
int w = 0, f = 1; char ch = getchar();
for(; !isdigit(ch); ch = getchar()) if(ch == '-') f = -1;
for(; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
return f * w;
}
inline void write(int x)
{
if(x < 0) {putchar('-'); x = ~(x - 1);}
int s[20], top = 0;
while(x) {s[++ top] = x % 10; x /= 10;}
if(! top)s[++ top] = 0;
while(top) putchar(s[top --] + '0');
}
bool SortCompare(Mapping first,Mapping second) {return first.Data < second.Data;}
void Prepare()
{
N = read(); BlockSize = 2.0 * sqrt(N);//100;//pow(N, 1.0 / 3.0);
BlockNum = N / BlockSize;
for(Re int i = 1; i <= BlockNum; i ++)
{
Fir[i] = (i - 1) * BlockSize + 1,
Las[i] = i * BlockSize;
for(Re int j = Fir[i]; j <= Las[i]; j ++) Belong[j] = i;
}
if(Las[BlockNum] < N)
{
BlockNum ++;
Fir[BlockNum] = Las[BlockNum - 1] + 1, Las[BlockNum] = N;
for(Re int j = Fir[BlockNum]; j <= Las[BlockNum]; j ++) Belong[j] = BlockNum;
}
for(Re int i = 1; i <= N; i ++) Tmp[i].Data = read(), Tmp[i].Rank = i;
std :: sort(Tmp + 1, Tmp + 1 + N, SortCompare);
for(Re int i = 1; i <= N; i ++)
{
if(Tmp[i].Data != Tmp[i - 1].Data) MaxData ++;
Original[MaxData] = Tmp[i].Data, Number[Tmp[i].Rank] = MaxData;
}
for(Re int i = 1; i <= BlockNum; i ++)
{
for(Re int j = Fir[i]; j <= Las[i]; j ++) Cnt[i][Number[j]] ++;
for(Re int j = 1; j <= MaxData; j ++) Cnt[i][j] += Cnt[i - 1][j];
for(Re int j = 1, times = 0; j <= i; times = 0, j ++) ///
for(Re int k = 1; k <= MaxData; k ++) ///
if(Cnt[i][k] - Cnt[j - 1][k] > times)
Mode[j][i] = k, times = Cnt[i][k] - Cnt[j - 1][k];
}
}
int Query(int L, int R)
{
int Bell = Belong[L], Belr = Belong[R], ret, maxcnt = 0;
if(Bell == Belr)
{
for(Re int i = L; i <= R; i ++)
if( (++ TmpCnt[Number[i]]) > maxcnt) ret = Number[i], maxcnt = TmpCnt[Number[i]];
else if(TmpCnt[Number[i]] == maxcnt) ret = std :: min(ret, Number[i]);
for(Re int i = L; i <= R; i ++) TmpCnt[Number[i]] --; ///
return Original[ret];
}
for(Re int i = L; i <= Las[Bell]; i ++) TmpCnt[Number[i]] ++;
for(Re int i = Fir[Belr]; i <= R; i ++) TmpCnt[Number[i]] ++;
ret = Mode[Bell + 1][Belr - 1]; maxcnt = TmpCnt[ret] + Cnt[Belr - 1][ret] - Cnt[Bell][ret];
for(Re int i = L; i <= Las[Bell]; i ++)
{
int NowCnt = TmpCnt[Number[i]] + Cnt[Belr - 1][Number[i]] - Cnt[Bell][Number[i]];
if(NowCnt > maxcnt || (NowCnt == maxcnt && ret > Number[i])) ret = Number[i], maxcnt = NowCnt;
TmpCnt[Number[i]] --;
}
for(Re int i = Fir[Belr]; i <= R; i ++)
{
int NowCnt = TmpCnt[Number[i]] + Cnt[Belr - 1][Number[i]] - Cnt[Bell][Number[i]];
if(NowCnt > maxcnt || (NowCnt == maxcnt && ret > Number[i])) ret = Number[i], maxcnt = NowCnt;
TmpCnt[Number[i]] --;
}
return Original[ret];
}
int main()
{
Prepare();
for(Re int i = 1; i <= N; i ++)
{
int L = read(), R = read();
write(LastAns = Query(L, R)); putchar('\n');
}
return 0;
}