P9212 「蓬莱人形」 题解
P9212 「蓬莱人形」 题解
性质分析
对于询问 \(\large(l,r,x,y,m)\) ,令 \(\large X=x\text{ mod }m,Y=y\text{ mod }m,A_i=a_i\text{ mod }m\) 并对其分类讨论。
- 若 \(\large X=Y\) ,答案为 \(\large0\) 。
- 若 \(\large X>Y\) ,则 \(\large a_i\) 合法当且仅当 \(\large A_i+X\geqslant m,A_i+Y<m\) 。答案为 \(\large\sum\limits_{i=l}^r[m-X\leqslant A_i<m-Y]\) 。
- 若 \(\large X<Y\) ,则 \(\large a_i\) 不合法当且仅当 \(\large A_i+X<m,A_i+Y\geqslant m\) 。答案为 \(\large r-l+1-\sum\limits_{i=l}^r[m-Y\leqslant A_i<m-X]\) 。
解法思考
需要支持求区间 \(\large[l,r]\) 中对 \(\large m\) 取模后数值在值域 \(\large[L,R]\) 中的 \(\large a_i\) 的个数。考虑转换为区间 \(\large[1,r]\) 的答案减去区间 \(\large[1,l-1]\) 的答案并离线处理,这样就不必记录每个前缀的答案,只需动态维护当前前缀的答案,可以大幅度减小时空开销。
发现有依赖于 \(\large m\) 较小的算法也有依赖于 \(\large m\) 较大的算法,因此可以根据数据规模分治处理。
对于所有 \(\large m\leqslant\sqrt V\) 分别开桶记录当前前缀在模 \(\large m\) 意义下的各数出现次数并暴力统计答案,修改和查询的时间复杂度都为 \(\large O(\sqrt V)\) 。
若 \(\large m>\sqrt V\) 则枚举 \(\large q=\lfloor\frac{a_i}m\rfloor\) 并统计数值在值域 \(\large[mq+L,mq+R]\) 中的 \(\large a_i\) 的个数,需要一种能实现单点加、区间和的数据结构。首先想到树状数组或线段树,单次修改和单次查询的时间复杂度都为 \(\large O(\log_2V)\) 。但因为 \(\large q\) 最多有 \(\sqrt V\) 个取值,因此总时间复杂度实际为 \(\large O(\sqrt V\log_2V)\) 无法接受。容易发现该数据结构单次查询的时间复杂度必须为 \(\large O(1)\) ,因此考虑维护前缀和并用其做差回答询问。然而暴力维护前缀和的时间复杂度为 \(\large O(V)\) 无法接受,于是以 \(\large \sqrt V\) 为块长进行分块,均摊后单次修改时间复杂度为 \(\large O(\sqrt V)\) 单次查询时间复杂度为 \(\large O(1)\) 。
两种算法的总时间复杂度都是 \(\large O(\sqrt V)\) ,成功利用根号分治将时间复杂度降为 \(\large O(\sqrt V)\) 。
AC 代码
#include <iostream>
#include <cstdio>
#include <cmath>
#include <vector>
using namespace std;
int n,q,t,tt,a[100001],tn1[321][321],tn2[321],tn3[100001],ans[500001];
vector<pair<int,int> >v[100001];
struct node {
int l,r,x,y,m;
}qu[500001];
int main() {
scanf("%d%d",&n,&q);
t=sqrt(n);
tt=(n-1)/t+1;
for(int i=1;i<=n;++i) scanf("%d",&a[i]);
int l,r,x,y,m;
for(int i=1;i<=q;++i) {
scanf("%d%d%d%d%d",&l,&r,&x,&y,&m);
x%=m;
y%=m;
if(x==y) continue;
if(x<y) {
swap(x,y);
ans[i]=r-l+1;
v[r].push_back({i,-1});
v[l-1].push_back({i,1});
}
else {
v[r].push_back({i,1});
v[l-1].push_back({i,-1});
}
qu[i]={l,r,m-x,m-y-1,m};
}
for(int i=1;i<=n;++i) {
for(int j=1;j<=t;++j) ++tn1[j][a[i]%j];
int bl=(a[i]-1)/t+1;
for(int j=bl+1;j<=tt;++j) ++tn2[j];
int ed=min(bl*t,100000);
for(int j=a[i];j<=ed;++j) ++tn3[j];
int siz=v[i].size();
for(int j=0;j<siz;++j) {
int id=v[i][j].first,x=qu[id].x,y=qu[id].y,m=qu[id].m,res=0;
if(m<=t)
for(int k=x;k<=y;++k) res+=tn1[m][k];
else
for(--x;x<=100000;x+=m,y=min(y+m,100000)) res+=tn2[(y-1)/t+1]+tn3[y]-tn2[(x-1)/t+1]-tn3[x];
ans[id]+=v[i][j].second*res;
}
}
for(int i=1;i<=q;++i) printf("%d\n",ans[i]);
return 0;
}

浙公网安备 33010602011771号