算法随笔——分块

介绍

分块的基本思想是通过适当的划分和预处理,用空间换时间,更加接近朴素算法,是一种暴力数据结构。

例题1

例如最经典的区间修改区间查询,若用树状数组来做就显得过于麻烦了。而用线段树做这道题,虽然通用,但马亮比较大,非常不友好。于是考虑分块。

思路

将整个序列分为 n 块,每个块长自然为 n,于是采用大段维护,小段朴素的思路。在查询或修改的范围 [l,r] 包含完整的块时,直接通过 add 数组标记该区间整体的变化。而左右两端零散的块直接朴素枚举,肥肠暴力。

代码

#include <bits/stdc++.h>
using namespace std;

#define ll long long
#define int ll
const int N = 2e5+5;

int n,m,a[N],L[N],R[N],pos[N],sum[N],add[N];

void modify(int l,int r,int d)
{
    int p = pos[l],q = pos[r];
    if (p == q) 
    {
        sum[p] += (r-l+1) * d;
        for (int i = l;i <= r;i++) a[i] += d;
    
        return;
    }
    
    for (int i = p+1;i <= q-1;i++) add[i] += d;
    for (int i = l ;i <= R[p];i++) a[i] += d;
    for (int i = L[q];i <= r;i++) a[i] += d;
    sum[p] += (R[p]-l+1) * d;
    sum[q] += (r-L[q]+1) * d;
}

int query(int l,int r)
{
    int p = pos[l],q = pos[r];
    ll ans = 0;
    if (p == q)
    {
        for (int i = l;i <= r;i++) ans += a[i] + add[p];
        return ans;
    }
    for (int i = p + 1;i <= q-1;i++) ans += sum[i] + add[i] * (R[i]-L[i]+1);
    for (int i = l;i <= R[p];i++) ans += a[i] + add[p];
    for (int i = L[q];i <= r;i++) ans += a[i] + add[q];
    
    return ans;
}


signed main()
{
    cin >> n >> m;
    for (int i = 1;i <= n;i++) cin >> a[i];
    int t = sqrt(n);
    
    for (int i = 1;i <= t;i++)
        L[i] = (i-1) * t + 1,R[i] = i * t;
    if (R[t] < n) t++,R[t] = n,L[t] = R[t-1] + 1;
    
    for (int i = 1;i <= t;i++)
        for (int j = L[i];j <= R[i];j++)
            pos[j] =  i,sum[i] += a[j];
    
    for (int i = 1;i <= m;i++)
    {
        char op[2];
        scanf("%s",op);
        int l,r,d;
        if (op[0] == 'Q')
        {
            cin >> l >> r;
            cout << query(l,r) << endl;
        }
        else 
        {
            cin >> l >> r >> d;
            modify(l,r,d);
        }
    }
    return 0;
}

例题2

蒲公英

一句话题意:给出 l,r,求出 [l,r] 的区间众数。

本题在线求解区间众数,众数不满足区间可加性,因此若用线段树和树状数组做较难维护,所以考虑分块。

做法

同样采用分块思想,将原序列分成 T 块,则块长为 L=NT

  • 预处理每个区间 [L,R] 中每个数出现的个数。这里的 L,R 是块的边界,一共有 T 块,所以时间复杂度为 O(T2)。同时每个区间都需要空间为 O(N) 的数组存储每个数出现的个数,空间复杂度为 O(NT2),记为 cntL,R

  • 而对于不在块中的查询范围 [l,L],[R,r] 可以用朴素扫描,在预处理好的 [L,R] 基础上累加,时间复杂度为 O(Q×NT)

QN 数量级相等,最终的时间复杂度为 O(NT2+N2T)
根据基本不等式:

NT2+N2T2×NT2N2T=2TN3

当且仅当 NT2=N2T,即 T=N3 时,原式取得最小值。
因此该算法时间复杂度为 O(N35),可以通过本题。

完整代码

点击查看代码
// Problem: 钂插叕鑻?
// Contest: AcWing
// URL: https://www.acwing.com/problem/content/251/
// Memory Limit: 256 MB
// Time Limit: 3000 ms
// Author: Eason
// Date:2024-01-18 09:47:03
// 
// Powered by CP Editor (https://cpeditor.org)

#include<bits/stdc++.h>
using namespace std;
#define ull unsigned long long
#define ll long long
#define INF 0x3f3f3f3f
#define re register
#define il inline
#define gc getchar
inline int read()
{
	int f=1,k=0;
	char c = getchar();
	while (c <'0' || c > '9')
	{
		if (c=='-') f=-1;
		c=getchar();
	}
	while(c >= '0' && c <= '9')  k = (k << 1)+(k << 3)+(c^48),c=getchar();
	return k*f;
}
const int N = 50005,M = 40;
int n,m,a[N];
int L[N],R[N],pos[N];
int block[M][M][N],mc[M][M],mck[M][M];
unordered_map<int,int> mp,timp;
int idx;
int main()
{
	cin >> n >> m;
	vector<int> v;
	for (int i = 1;i <= n;i++) 
	{
		cin >> a[i];
		v.push_back(a[i]);
 	}
 	
 	sort(v.begin(),v.end());
	v.erase(unique(v.begin(),v.end()),v.end());
 	for (int i = 0;i < v.size();i++) mp[v[i]] = i;
 	for (int i = 1;i <= n;i++) a[i] = mp[a[i]]; 
 	int T = cbrt(n),len = n/T;
	for (int i = 1;i <= T;i++)
		L[i] = (i-1) * len+1,R[i] = i * len;
	if (R[T] < n) T++,R[T] = n,L[T] = R[T-1] + 1;
	for (int i = 1;i <= T;i++)
		for (int j = L[i];j <= R[i];j ++)
			pos[j] = i;
	for (int i = 1;i <= T;i++)
		for (int j = i;j <= T;j++)
		{
			int maxn = -1e9,maxk;
			for (int k = L[i];k <= R[j];k++)
			{

				block[i][j][a[k]] ++;
				if (block[i][j][a[k]] > maxn) maxn = block[i][j][a[k]],maxk = a[k];
				else if (block[i][j][a[k]] == maxn) 
				{
					if (a[k] <= maxk) maxn = block[i][j][a[k]],maxk = a[k];
				} 
			}
			mc[i][j] = maxn;
			mck[i][j] = maxk;
		}
	int x = 0;
	for (int i = 1;i <= m;i++)
	{
		int le,ri;
	 	cin >> le >> ri;
	 	le = (le +x-1) % n + 1,ri = (ri+x-1) % n + 1;
	 	if (le > ri) swap(le,ri);
		int pl = pos[le],pr = pos[ri],csmc = mc[pl+1][pr-1],csmck = mck[pl+1][pr-1];	
		
		if (pl == pr)
		{
			unordered_map<int,int> mp;
			int maxn = -1e9,maxk = -1;
			for (int j = le;j <= ri;j++)  mp[a[j]]++;
			for (int j = le;j <= ri;j++) if (mp[a[j]] > maxn || (mp[a[j]] == maxn && a[j] < maxk))  maxn = mp[a[j]],maxk = a[j];
			cout << v[maxk] << endl;
			x= v[maxk];
			mp.clear();
			continue;
  		}
		for(int k = le;k <= R[pl];k++) 
		{
			 block[pl+1][pr-1][a[k]]++;
			 if (block[pl+1][pr-1][a[k]] > mc[pl+1][pr-1]) mc[pl+1][pr-1] = block[pl+1][pr-1][a[k]],mck[pl+1][pr-1] = a[k];
			else if (block[pl+1][pr-1][a[k]] == mc[pl+1][pr-1]) 
			{
				if (a[k] <= mck[pl+1][pr-1]) mc[pl+1][pr-1] = block[pl+1][pr-1][a[k]],mck[pl+1][pr-1] = a[k];
			}
		}
		for (int k = L[pr];k <= ri;k++)
		{
			 block[pl+1][pr-1][a[k]]++;
			 if (block[pl+1][pr-1][a[k]] > mc[pl+1][pr-1]) mc[pl+1][pr-1] = block[pl+1][pr-1][a[k]],mck[pl+1][pr-1] = a[k];
			else if (block[pl+1][pr-1][a[k]] == mc[pl+1][pr-1]) 
			{
				if (a[k] <= mck[pl+1][pr-1]) mc[pl+1][pr-1] = block[pl+1][pr-1][a[k]],mck[pl+1][pr-1] = a[k];
			}
		}
		cout << (x = v[mck[pl+1][pr-1]]) << endl;
		for(int k = le;k <= R[pl];k++) 
			 block[pl+1][pr-1][a[k]]--;
		for (int k = L[pr];k <= ri;k++)
			 block[pl+1][pr-1][a[k]]--;
		
		mc[pl+1][pr-1] = csmc,mck[pl+1][pr-1] = csmck;
	}
	return 0;
}
posted @   codwarm  阅读(20)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示