「分块」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 :

愿望

posted @ 2019-12-28 20:34  Luckyblock  阅读(516)  评论(7编辑  收藏  举报