「分塊」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)\) .

對於查詢操作, 不完整塊在暴力修改時 順便暴力查詢, 完整塊分下列兩種情況討論 :

  1. 有懶標記 , 則塊內元素相同 , 直接判斷標記與查詢值 是否相同.
  2. 無懶標記 , 沒有什么好的處理方法 , 直接暴力查詢.

單次查詢操作, 處理不完整塊復雜度為 \(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;
}

At Last :

願望


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM