C/C++分块个人题单

分块题单

TIP:文章分为题目与题解两部分,在独立思考题目后再观看题解效果会更好

题目:

1.开关(个人认为难度是最高的) 题目地址
2.守墓人 题目地址
3.树状数组2 题目地址
4.树状数组1 题目地址
5.[USACO07JAN]Balanced Lineup G 题目地址
6.线段树1 题目地址
7.线段树2 题目地址
8.XOR的艺术(和开关差不多) 题目地址
9.质量检测 题目地址

分块:时间复杂度为 O ( ( N + Q ) N ) O((N + Q)\sqrt{N}) O((N+Q)N )
分块只能维护具有区间可加性的值,如:最大/小值,区间和等。什么是区间可加性?举个栗子:你知道一个区间A,区间B的最大值,那么你肯定也知道A拼接上B的最大值(max(A_max,b_max)),故最大值是具有区间可加性的。

------------------------------------我是分割线---------------------------------

题解

总体采用:“整块维护、局部朴素”的思想,即:对于查询和修改的区间,暴力更新不成块的头和尾,维护整块。

1.开关(区间反转):

#include<iostream>
#include<cstdio>
#include<cmath>
#define ll long long
#define INF 0x3f3f3f3f
#define re register
#define N (ll)1e5 + 10

using namespace std;
ll x[N],y[N],n,Q,pos[N],sum[N];
//x[i]:第i块的左端点,y[i]:第i块的右端点
//pos[i]:原数组中第i个元素在哪一块
//sum[i]:第i块中亮着灯的数量
bool a[N],add[N];
//add[i]:第i块需不需要反转(为了整块的维护)
inline void change(ll,ll);
inline ll ask(ll,ll);
int main()
{
   
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	cin >> n >> Q;
	ll num = sqrt(n);
	for(int i = 1;i <= num;++i)
	{
   
		x[i] = (i - 1) * num + 1;
		y[i] = i * num;
		for(re int j = x[i];j <= y[i];++j) pos[j] = i;
	}
	if(y[n] < n)
	{
   
		++num;
		x[num] = y[num - 1] + 1;
		y[num] = n;
		for(re int j = x[num];j <= y[num];++j) pos[j] = num;
	}
	
	while(Q--)
	{
   
		ll t,l,r;
		cin >> t >> l >> r;
		if(t)
		{
   
			cout << ask(l,r) << endl;
		}
		else change(l,r);
	}
	return 0;
}

inline void change(ll l,ll r)
{
   
	ll p = pos[l],q = pos[r];
	if(p == q)
	{
   
		ll t = 0;
		for(int i = l;i <= r;++i)//暴力更新
		{
   
			sum[p] -= (a[i] ^ add[p]);
			a[i] ^= 1;
			sum[p] += (a[i] ^ add[p]);
		}
	}
	else
	{
   
		for(int i = l;i <= y[p];++i)
		{
   
			sum[p] -= (a[i] ^ add[p]);
			a[i] ^= 1;
			sum[p] += (a[i] ^ add[p]);
		}
		for(int i = p + 1;i <= q - 1;++i)//维护整块
		{
   
			add[i] ^= 1,sum[i] = (y[i] - x[i] + 1) - sum[i];
		}
		for(int i = x[q];i <= r;++i)
		{
   
			sum[q] -= (a[i] ^ add[q]);
			a[i] ^= 1;
			sum[q] += (a[i] ^ add[q]);
		}
	}
	return ;
}

inline ll ask(ll l,ll r)
{
   
	ll p = pos[l],q = pos[r],ans = 0;
	if(p == q)
	{
   
		for(int i = l;i <= r;++i) ans += (a[i] ^ add[p]);
	}
	else
	{
   
		for(int i = l;i <= y[p];++i)
		{
   
			ans += (a[i] ^ add[p]);
		}
		for(int i = p + 1;i <= q - 1;++i)
		{
   
			ans += sum[i];
		}
		for(int i = x[q];i <= r;++i)
		{
   
			ans += (a[i] ^ add[q]);
		}
	}
	return ans;
}

运行速度(没开O2):
图1


2.守墓人(单点修改/求值,区间修改/求值)

#include<iostream>
#include<cstdio>
#include<cmath>
#define ll long long
#define INF 0x3f3f3f3f
#define re register
#define N 2 * 100000 + 10

using namespace std;
ll x[N],y[N],sum[N],add[N],n,Q,a[N],pos[N];
//x[i]:第i块的左端点,y[i]:第i块的右端点
//pos[i]:原数组中第i个元素在哪一块
//sum[i]:第i块的风水值之和
//add[i]:第i块中所有元素应该加上的值
inline void change(ll,ll,ll);
inline ll ask(ll,ll);
int main()
{
   
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	cin >> n >> Q;
	ll num = sqrt(n);
	for(re int i = 1
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值