\(\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;
}