Loading

Ynoi 大分块选做

第二分块

link

CF 版本

题意:给出一个序列 \(a_{1...n}\),有 \(m\) 次操作。每次:

  • 修改:给出 \(l,r,x\),表示把区间 \([l,r]\)\(>x\) 的数减去 \(x\)

  • 查询:给出 \(l,r,x\),求 \([l,r]\) 中有多少个数 \(=x\)

\(1\le n\le 10^6,\space 1\le m\le 5\times 10^5,\space 0\le a_i,x\le 10^5+1\),时间限制 \(7.5\text{s}\),空间限制 \(64\text{MB}\)

考虑分块,块长为 \(B\)

发现值域只有 \(0...10^5+1\),我们需要从这方面入手。

考虑整块的操作,每个块维护 \(t_x\) 表示初始为 \(x\) 的数值在操作后变成的数,以及 \(s_x\) 表示当前 \(=x\) 的数字个数。

设块内数字最大值为 \(mx\),减去的数为 \(x\),分类讨论:

  • \(x\ge\dfrac {mx}2\):每个数减一次之后一定 \(\le x\),值域上遍历一下 \(x+1...mx\),更新这些值的 \(t\)

  • \(x<\dfrac {mx}2\):此时如果更新 \(t\) 会发现无法处理。考虑让 \(\le x\) 的数加上 \(x\),然后打个整体减 \(x\) 标记。

对于散块的操作,暴力更新块内值,然后重新初始化 \(t\)\(s\)

发现初始化难搞,我们可以把 \(t\) 数组映射到块内元素上。具体的,设 \(r_x\) 表示块内第一个 \(=x\) 的数出现位置,设 \(t_i\) 表示 \(a_i\) 当前的值为 \(a_{t_i}\)

用并查集维护 \(t\)。对于根 \(i\),因为 \(t_i=i\) 不能递归,维护 \(v_i\) 表示这个根的数值即可。

但是空间是 \(O(\dfrac {n^2}B)\) 的,无法通过。考虑一个 trick,因为每个块是独立的,所以可以在最外层枚举块,然后依次扫描每个操作。

点击查看代码
#include<bits/stdc++.h>
#define ll int
#define ull unsigned ll
#define mkp make_pair
#define fi first
#define se second
#define pir pair<ll,ll>
#define pb push_back
using namespace std;
const ll maxn=1e6+10;
ll B=1000,op[maxn][4],L,R,mx,tag;
ll n,m,a[maxn],d[maxn],rt[maxn],val[maxn],siz[maxn],ans[maxn],o[maxn],g;
inline ll find(ll x){
	ll &dx=d[x];
	return x==dx? x:dx=find(dx);
}
inline void merg(const ll &x,const ll &y){
	ll &rx=rt[x], &ry=rt[y];
	if(ry) d[rx]=ry;
	else{
		ry=rx;
		val[ry]=y;
	}
	siz[y]+=siz[x], rx=siz[x]=0;
}
inline void build(){
	mx=tag=0;
	for(register ll i=L;i<=R;++i){
		ll ai=a[i];
		mx=max(mx,ai);
		if(rt[ai]) d[i]=rt[ai];
		else rt[ai]=d[i]=i, val[i]=ai;
		++siz[ai];
	}
}
inline void block_change(const ll &x){
	if(mx-tag>2*x){
		for(register ll i=tag+1;i<=tag+x;++i)
			merg(i,i+x);
		tag+=x;
	} else{
		for(register ll i=tag+x+1;i<=mx;++i)
			merg(i,i-x);
		mx=min(mx,tag+x);
	}
}
inline void clr(){
	for(ll i=L;i<=R;++i){
		a[i]=val[find(i)];
		rt[a[i]]=siz[a[i]]=0;
		a[i]-=tag;
	}
	for(ll i=L;i<=R;i++) val[i]=d[i]=0;
}
inline void part_change(const ll &l,const ll &r,const ll &x){
	for(ll i=L;i<=R;++i){
		a[i]=val[find(i)];
		rt[a[i]]=siz[a[i]]=0;
		a[i]-=tag;
	}
	for(ll i=L;i<=R;i++) val[i]=d[i]=0;
	for(ll i=l;i<=r;++i)
		if(a[i]>x) a[i]-=x;
	mx=tag=0;
	for(register ll i=L;i<=R;++i){
		ll ai=a[i];
		mx=max(mx,ai);
		if(rt[ai]) d[i]=rt[ai];
		else rt[ai]=d[i]=i, val[i]=ai;
		++siz[ai];
	}
}
inline void query(const ll &l,const ll &r,const ll &x,ll &res){
	if(l==L&&r==R){
		if(x+tag<=1e5+1) res+=siz[x+tag]; return;
	}
	for(ll i=l;i<=r;++i)
		if(val[find(i)]-tag==x) ++res;
}
inline void rd(ll &x){
	char c;
	while(!isdigit(c=getchar())) ;
	x=c-'0';
	while(isdigit(c=getchar())) x=x*10+c-'0';
}
int main(){
	rd(n), rd(m); B=sqrt(n);
	for(register ll i=1;i<=n;++i) rd(a[i]);
	for(register ll i=1;i<=m;++i){
		rd(op[i][0]), rd(op[i][1]), rd(op[i][2]), rd(op[i][3]);
	}
	ll o=0;
	while(o<n){
		L=o+1, R=min(n,o+=B);
		build();
		for(ll j=1;j<=m;++j){
			ll t=op[j][0], l=op[j][1], r=op[j][2], x=op[j][3];
			if(t==1){
				if(x==0||r<L||l>R) continue;
				if(l<=L&&R<=r){
					block_change(x);
				} else{
					part_change(max(l,L),min(r,R),x);
				}
			}
			else query(max(l,L),min(r,R),x,ans[j]);
		}
		clr();
	}
	for(register ll i=1;i<=m;i++)
		if(op[i][0]==2) printf("%d\n",ans[i]);
	return 0;
}

第十四分块

link

题意:一个序列 \(a_{1...n}\)\(m\) 次询问,每次给出 \(l,r\),求有多少对有序二元组 \((i,j)\) 满足 \(a_i|a_j\)

\(1\le n,m,a_i\le 5\times 10^5\),时间限制 \(\text{3s}\),空间限制 \(128\text{MB}\)

考虑莫队。每次插入/删除都需要查询一下,考虑二次离线。

然后我们的问题转化为:

  1. \(\forall i\in [1,n]\)\(a_{1...i-1}\)\(a_i\) 一共产生多少对合法二元组。

  2. 支持每次插入一个数,\(O(1)\) 询问数集中 \(x\) 的因数和倍数的总个数。

我们单次枚举因数的时间复杂度可以视为 \(O(\sqrt a)\),和莫队同阶。

因此第一个问题直接枚举因数即可,考虑第二个问题。

对于插入的 \(>\sqrt a\) 的数,可以直接枚举因数和倍数。

对于 \(<\sqrt a\) 的数,可以枚举因数,但不能直接枚举倍数。

现在的问题是如何处理 \(<\sqrt a\) 的数字作为因数的贡献。

考虑枚举数字 \(x<\sqrt a\),计算 \(x\) 的贡献。对于二次离线三元组 \((l,r,pos)\),贡献是 “\(a_{1...pos}\)\(x\) 的个数 \(\times\) \(a_{l...r}\) 中是 \(x\) 的倍数的个数”,两个东西都可以直接扫一遍 \(1...n\) 求。

代码不难写,时间复杂度根号。

点击查看代码
#include<bits/stdc++.h>
#define ll int
#define ull unsigned ll
#define mkp make_pair
#define fi first
#define se second
#define pir pair<ll,ll>
#define pb push_back
using namespace std;
const ll maxn=5e5+10, inf=1e9;
ll n,m,a[maxn],bl[maxn],B,cnt[maxn],cnt2[maxn],lim=100,len[maxn],c;
long long ans[maxn],sum[maxn],sum2[maxn];
struct seg{
	ll l,r,id;
}q[maxn];
bool cmp(seg a,seg b){
	if(bl[a.l]==bl[b.l]) return bl[a.l]&1? a.r<b.r:a.r>b.r;
	return a.l<b.l;
}
struct node{
	ll l,r,k,id;
	ll pos;
}w[maxn<<1];
ll tot,hd[maxn],nxt[maxn<<1];
void ins(ll u,node t){
	w[++tot]=t;
	nxt[tot]=hd[u], hd[u]=tot;
	w[tot].pos=u;
}
vector<ll>fac[maxn];
void rd(ll &x){
	char c;
	while(!isdigit(c=getchar())) ;
	x=c-'0';
	while(isdigit(c=getchar())) x=(x<<1)+(x<<3)+c-'0';
}
int main(){
	rd(n), rd(m);
	B=n/sqrt(m);
	for(ll i=1;i<=n;i++) rd(a[i]), bl[i]=(i-1)/B+1;
	for(ll i=1;i<=5e5;i++)
		for(ll j=i;j<=5e5;j+=i) fac[j].pb(i);
	for(ll i=1;i<=n;i++){
		sum[i]=sum[i-1]; ll s=cnt2[a[i]];
		for(ll j:fac[a[i]])
			s+=cnt[j], ++cnt2[j];
		sum[i]+=s; ++cnt[a[i]];
		sum2[i]=sum2[i-1]+s+2;
	}
	for(ll i=1;i<=m;i++){
		rd(q[i].l), rd(q[i].r);
		q[i].id=i, len[i]=q[i].r-q[i].l+1;
	}
	sort(q+1,q+1+m,cmp);
	ll l=1, r=0;
	for(ll i=1;i<=m;i++){
		if(r<q[i].r){
			ans[q[i].id]+=sum[q[i].r]-sum[r];
			ins(l-1,(node){r+1,q[i].r,-1,q[i].id});
			r=q[i].r;
		}
		if(l>q[i].l){
			ans[q[i].id]+=-sum2[l-1]+sum2[q[i].l-1]; 
			ins(r,(node){q[i].l,l-1,1,q[i].id});
			l=q[i].l;
		}
		if(r>q[i].r){
			ans[q[i].id]+=-sum[r]+sum[q[i].r];
			ins(l-1,(node){q[i].r+1,r,1,q[i].id});
			r=q[i].r;
		}
		if(l<q[i].l){
			ans[q[i].id]+=sum2[q[i].l-1]-sum2[l-1];
			ins(r,(node){l,q[i].l-1,-1,q[i].id});
			l=q[i].l;
		}
	}
	memset(cnt,0,sizeof cnt);
	memset(cnt2,0,sizeof cnt2);
	for(ll i=1;i<=n;i++){
		ll x=a[i];
		for(ll j:fac[x]) ++cnt[j];
		if(x>lim){
			for(ll j=x;j<=5e5;j+=x) ++cnt[j];
		}
		for(ll j=hd[i];j;j=nxt[j]){ 
			node t=w[j]; long long s=0;
			for(ll j=t.l;j<=t.r;j++)
				s+=cnt[a[j]];
			ans[t.id]+=t.k*s;
		}
	}
	for(ll i=1;i<=lim;i++){
		for(ll j=i;j<=n;j+=i) cnt[j]=i;
		for(ll j=1;j<=n;j++){
			ll x=a[j];
			sum[j]=sum[j-1]+(cnt[x]==i);
			sum2[j]=sum2[j-1]+(x==i);
		}
		for(ll j=1;j<=tot;j++){
			node t=w[j];
			ans[t.id]+=1ll*t.k*sum2[t.pos]*(sum[t.r]-sum[t.l-1]);
		}
	}
	for(ll i=1;i<=m;i++) ans[q[i].id]+=ans[q[i-1].id];
	for(ll i=1;i<=m;i++) printf("%lld\n",ans[i]+len[i]);
	return 0;
}
posted @ 2024-03-12 18:24  Lgx_Q  阅读(31)  评论(0编辑  收藏  举报