Ynoi 大分块选做
第二分块
题意:给出一个序列 \(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;
}
第十四分块
题意:一个序列 \(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}\)
考虑莫队。每次插入/删除都需要查询一下,考虑二次离线。
然后我们的问题转化为:
-
求 \(\forall i\in [1,n]\),\(a_{1...i-1}\) 与 \(a_i\) 一共产生多少对合法二元组。
-
支持每次插入一个数,\(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;
}