Nityacke's 分块(代码待补)(未补全)

发布时间 2023-09-25 21:13:38作者: British_Union

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}\) 种。可以使用哈希表维护。()

回滚莫队:
对于左端点在同一个块的东西:
先把右端点向右(这是单调的),左端点再向左,然后撤销回来。
不加莫队是等价的。

image

P8078 [WC2022] 秃子酋长

给一个长为 \(n\) 的排列 \(a_1,\dots, a_n\),有 \(m\) 次询问,每次询问区间 \([l, r]\) 内,排序后相邻的数在原序列中的位置的差的绝对值之和。
删除的话是容易使用链表维护的。于是就是不加莫队板子。

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\)为大小分了块,每块大小相同。散快前缀和即可。