Nityacke's 分块(未补全)
P2801 教主的魔法
区间加区间查询一个数排名。
对于每个块,维护其有序序列。修改时散块暴力重构,整块打tag。
查询是简单的。时间复杂度 \(O(n\log B+\dfrac{qn}{B}\log B+qB)\)。
\(B=\sqrt{n\log n}\)时复杂度为\(O(n\sqrt{n}\log n)\)。
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e6+5;
int a[maxn],bel[maxn],n,q,B,cnt;
vector<int> e[5005];
int tag[5005],L[5005],R[5005];
void rebuild(int u){//
for(int i=L[u];i<=R[u];i++)a[u]+=tag[u];
e[u].clear();
for(int i=L[u];i<=R[u];i++)e[u].push_back(a[i]);
sort(e[u].begin(),e[u].end());
tag[u]=0;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
cin>>n>>q;
B=1500;
for(int i=1;i<=n;i++){
cin>>a[i];
bel[i]=(i+B-1)/B;
e[bel[i]].push_back(a[i]);
}
cnt=(n+B-1)/B;
for(int i=1;i<=cnt;i++){
L[i]=R[i-1]+1;R[i]=L[i]+e[i].size()-1;
sort(e[i].begin(),e[i].end());
}
for(int i=1;i<=q;i++){
char tp;
int l,r,c;
cin>>tp>>l>>r>>c;
if(tp=='M'){
if(bel[l]==bel[r]){
for(int j=l;j<=r;j++)a[j]+=c;
rebuild(bel[l]);
}else{
for(int j=l;j<=R[bel[l]];j++)a[j]+=c;
rebuild(bel[l]);
for(int j=L[bel[r]];j<=r;j++)a[j]+=c;
rebuild(bel[r]);
for(int j=bel[l]+1;j<bel[r];j++)tag[j]+=c;
}
}else{
int res=0;
if(bel[l]==bel[r]){
for(int j=l;j<=r;j++){
if(a[j]+tag[bel[l]]>=c)res++;
}
}else{
for(int j=l;j<=R[bel[l]];j++){
if(a[j]+tag[bel[l]]>=c)res++;
}
for(int j=L[bel[r]];j<=r;j++){
if(a[j]+tag[bel[r]]>=c)res++;
}
for(int j=bel[l]+1;j<bel[r];j++){
int pos=lower_bound(e[j].begin(),e[j].end(),c-tag[j])-e[j].begin();
res+=(R[j]-L[j]+1)-pos;
}
}
cout<<res<<endl;
}
}
return 0;
}
P5356 [Ynoi2017] 由乃打扑克
区间加区间第k小。
修改同上题,查询将两散块归并起来,然后进行二分答案。
复杂度 \(O(B+\dfrac{n}{B}+q(B+\dfrac{n}{B}\log B\log V))\)。
取 \(B=\sqrt{n}\log n\),复杂度 \(O(n\sqrt{n}\log V)\)。
口胡完毕,代码没调。
P2496 体育课
区间加等差数列公差为正,区间最值。
修改在每个块上打两个标记:整体加和公差d。
修改时散块暴力重构,整块打标记。
每块维护最大值位置,显然这个单调递增。整块处理时check一下就可以了。
P3604 美好的每一天
HOI4战犯表示不理解一堆人跳楼带来的美感
询问小写字符串区间中有多少个子区间是可以被重排为回文串的。
重排为回文串的条件是至多有一种字母个数是奇数。
考虑状压字符出现次数,即是否为 \(0\) 或 \(2^k\)。
考虑前缀异或,一个串可以被表示为 \(s_r\ \text{xor}\ s_{l-1}\)。
于是加入一个字符的时候,贡献就可以由被这个字符位置上为 \(1\) 的这个数异或上 \(2^k\) 或 \(0\) 得到。
莫队,同时维护哈希表即可。
P5072 [Ynoi2015] 盼君勿忘
询问一个区间所有子序列的分别去重后的和 \(\bmod\) 给定的 \(p\)(不同)
如果一个数 \(x\) 在长度为 \(len\) 的区间出现了 \(cnt\) 的贡献:\(x2^{cnt}-x2^{len-cnt}\)。
第一个是容易的,而第二个可以把 \(cnt\) 相同的东西一起计算。
而不同的 \(cnt\) 只有 \(\sqrt{n}\) 种。可以使用哈希表维护。()
回滚莫队:
对于左端点在同一个块的东西:
先把右端点向右(这是单调的),左端点再向左,然后撤销回来。
不加莫队是等价的。
P8078 [WC2022] 秃子酋长
给一个长为 \(n\) 的排列 \(a_1,\dots, a_n\),有 \(m\) 次询问,每次询问区间 \([l, r]\) 内,排序后相邻的数在原序列中的位置的差的绝对值之和。
删除的话是容易使用链表维护的。于是就是不加莫队板子。
C765F Souvenirs
求出区间内差的绝对值的最小值。
考虑设 \(d(i,j,0)\) 为 \(i\) 所在块的 \(l_i\sim i\) 到第 \(i+1\sim j\) 块的贡献。
\(d(i,j,1)\) 则为 \(i\) 所在块的 \(i\sim r_i\) 到 \(j\sim i-1\) 块的答案。
显然我们可以先算出一个点到一个块,再前缀/后缀 \(\max\) 算出一段前缀/后缀到一个块,再前缀/后缀得到 \(d\) 数组。
对于一个点到一个块:
首先我们把每个串内部排序并记下来每个位置对应排序后哪个位置。(这里顺便解决了同一块的询问和散块贡献)
此部分复杂度 \(O(n\log B)\)。
在 \(i\) 块排序后数组枚举,可以双指针得到答案。此部分连同前缀后缀 \(\max\) 复杂度 \(O(n\sqrt n)\)。
这样处理了整散块贡献。
散块之间直接在排序之后的数组上归并起来即可。
对于整块到整块,考虑预处理 \(S(i,j)\) 为第 \(i\sim j\) 串答案。
首先容易处理块内答案。然后可以用 \(d\) 转移。此部分复杂度 \(O(n)\)。
综上,我们使用 \(O(n\sqrt n)\) 复杂度解决了问题。
值得一提的是,本题存在较多种解法,回滚莫队+链表、回滚莫队+bitset 以及另一种分块等均能解决该问题。
BZOJ4358 permu
给出一个长度为 \(n\) 的排列 P,以及 \(m\) 个询问。每次询问某个区间 \([l,r]\) 中,最长的值域连续段长度。
加入时用链表合并即可。
P5386 [Cnoi2019] 数字游戏
给定一个排列,多次询问,求一个区间 \([l,r]\) 有多少个子区间的值都在区间 \([x,y]\) 内。
就是求所有连续 \(01\) 的 \(\binom{len}{2}\)。
但是按秩合并并查集是 \(O(\log)\) 的,很不好。
考虑对 \(1\sim n\) 分块+链表维护,可以做到\(O(1)-O(\sqrt{n})\),正好契合莫队复杂度需求,还支持撤销。
P6578 [Ynoi2019] 魔法少女网站
不是你没有自杀就玩不了游戏是吗
单点带修,询问有区间多少子区间最大值 \(\le x\)。
对操作分块:对询问按 \(x\) 排序,顺次加入数,和上题一样的序列分块维护不修改的点。
对于每个询问,做一遍操作,然后再撤销操作即可。
\(O(n\sqrt{n})\)/
P3674 小清新人渣的本愿
询问区间是否能找到两个数和/差/积为给定数。
维护前缀bitset。和差就移位搞下就可以了。
然后积直接暴力分解。\(O(n(\sqrt{n}+\dfrac{n}{w}))\)。
P4688 [Ynoi2016] 掉进兔子洞
lxl还是身体力行一下跳楼吧!
询问三个区间,把三个区间中同时出现的数一个一个删掉,问最后三个区间剩下的数的个数和,询问独立。
考虑每个数的负贡献。就是三个区间的和减去重复出现的数的值\(\times 3\)。
改一下离散化方法,改成排排名的方法:1 2 2 3 3
\(\to\)1 2 2 4 4
。
这个时候可以莫队搞。注意询问需要分下组,避免空间炸炸炸。
(早期暴戾代码)
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5,M=N/3+10;
int n,m,maxn;
int a[N],ans[M],cnt[N];
bitset<N> sum[M],cur;
struct Q
{
int l,r,k;
bool operator <(const Q &x)const
{
if(l/maxn!=x.l/maxn)return l<x.l;
return (l/maxn)&1?r<x.r:r>x.r;//奇偶排序
}
}q[M*3];
int t[N];
void lsh()
{
for(register int i=1;i<=n;++i)t[i]=a[i];
stable_sort(t+1,t+n+1);
for(register int i=1;i<=n;++i)a[i]=lower_bound(t+1,t+n+1,a[i])-t;
}
void add(int x)
{
cur.set(x+cnt[x]++);
}
void sub(int x)
{
cur.reset(x+--cnt[x]);
}
void solve()
{
int nowcnt=0,fuck;
cur.reset();
for(fuck=0;fuck<M-5&&m;++fuck)
{
--m;
ans[fuck]=0;
sum[fuck].set();
for(register int j=0;j<3;++j)
{
cin>>q[nowcnt].l>>q[nowcnt].r;
q[nowcnt].k=fuck;
ans[fuck]+=q[nowcnt].r-q[nowcnt].l+1;
++nowcnt;
}
}
stable_sort(q,q+nowcnt);
for(register int i=0,l=1,r=0;i<nowcnt;++i)
{
while(l>q[i].l)add(a[--l]);
while(r<q[i].r)add(a[++r]);
while(l<q[i].l)sub(a[l++]);
while(r>q[i].r)sub(a[r--]);
sum[q[i].k]&=cur;
}
for(register int i=0;i<fuck;++i)
{
cout<<ans[i]-(int)sum[i].count()*3<<endl;
}
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin>>n>>m;
for(int i=1;i<=n;i++)cin>>a[i];
lsh();
maxn=n/sqrt(m);
solve();memset(cnt,0,sizeof(cnt));solve();memset(cnt,0,sizeof(cnt));solve();
return 0;
}
P5309 [Ynoi2011] 初始化
修改给定\(x,y(y<x)\),\(a_{y+kx}+=v\)。区间和。
根号分治。对于 \(x>\sqrt{n}\),\(O(1)-O(\sqrt{n})\)分块。
对于\(x\le \sqrt{n}\),记修改 \(x\) 的 \(y\) 的 \(v\) 的前缀后缀和。
注意到其实是对\(x\)为大小分了块,每块大小相同。散快前缀和即可。
P5397 [Ynoi2018] 天降之物
终于正常一点
序列 a,两种操作。
把所有 \(x\) 变成 \(y\)。查询最小的 \(|i − j|\) 使得 \(a_i = x, a_j = y\)。
根号分治,先考虑无修改的情况。
对于每个值维护其出现的位置集合。如果超过 \(B\) 就预处理出他到每个小点的答案。
如果小于 \(B\) 询问时就直接归并位置集合。
修改:
考虑对大点维护一个“没有被处理的小点集合”。
修改:小-小:直接合并;小-大 扔进没处理里面,到了\(B\)就重构;大大直接合并重构。
查询:归并未处理集合,查询已处理答案。复杂度竟然是对的。