分块题单
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):
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