题传
做完 CF1422F 再做这道题就肥肠有感觉了。
如果你不想再看一题那么我就无耻推销一下 我的题解。
请确保你已经知道了 CF1422F 的做法。
简化题意:多次询问,求 \(\sigma_0 (\prod_{i=l}^r a_i)\)。
我会积性函数线性筛
设素数集 \(P=\{p_1, p_2, \dots p_m \}\),则序列中的数 \(a_i\) 可表示成:
\[\prod_{j=1}^m p_j^{c_{i, j}} \]那么根据约数的定义(选出质因子的组合),有:
\[\sigma_0 (\prod_{i=l}^r a_i)=\prod_{j=1}^{m} (1+\sum_{i=l}^{r} c_{i, j}) \]现在目标非常明确:维护上面这一坨东西!
由于增减一个数会在很多个括号里产生改动,所以显然不能用线段树等在线数据结构乱搞了,考虑离线下来莫队。
设 \(cnt_j =\sum_{i=l}^r c_{i, j}\)
显然不能每次移动一个数修改所有质因子的 \(cnt\),这时候你想到了 CF1422F 的做法,根号分治,\(\sqrt{Max_a}\approx 31623=R\)。
对于 \(\le R\) 的素数,前缀和记下 \(a_i\) 的 \(c\),询问的时候,暴力枚举这些数,把贡献算上(注意这里的“询问”指的不是在莫队里面搞,而是直接做)
对于 \(\ge R\) 的素数,只有一个,所以直接上莫队,每次直接对应修改这个数。
然后打个表,发现 \(\le R\) 的素数有 \(3000\) 多个,空间飞天,怎么办?
考虑到这题对阈值上部分的要求没那么紧(不像 CF1422F 那样要求区间不同种类数且指数为 \(1\)),也就是说,我们在莫队的时候多处理几个素数是没问题的,所以我们珂以适当地调低阈值,这里选择 \(R=p_{239}=1499\),然后就珂以愉快地跑过去了。
复杂度大概是 \(O(239(n+q) + k n \sqrt q)\),\(k\) 是一个小常数,不超过 \(3\)(因为 \(3\) 个 \(10^3\) 乘起来就爆值域了)。
注意大质因子要离散化,这里使用 map。
#include <stdio.h> #include <algorithm> #include <string.h> #include <cctype> #include <cmath> #include <map> #include <vector> using namespace std; typedef long long ll; typedef pair <int, int> PI; inline int read(){ char ch=getchar();int x=0, f=1; while(!isdigit(ch)){if(ch=='-') f=-1; ch=getchar();} while(isdigit(ch)){x=(x<<3)+(x<<1)+ch-'0';ch=getchar();} return x*f; } const int mo=19260817; inline int ksm(int a, int b){ int ret=1;for(; b; b>>=1, a=1ll*a*a%mo) if(b&1) ret=1ll*ret*a%mo; return ret; } const int N=1e5+1; const int R=31623;//sqrt(1000000000) int tot, Su[3402], u[R];//prime=3401; int sum[N][240], a[N], n, m, Mx, up; int newcnt; map <int, int> Mp; vector <PI> pri[N]; int Getc(int &x, int p){ if(x%p) return 0;int res=0; while(!(x%p)) res++, x/=p; return res; } void Reverse(int id){//分解 int S=a[id]; for(int i=up+1; i<=tot; i++) if(S%Su[i]==0){ int x=Getc(S, Su[i]); pri[id].push_back(PI(i, x)); } if(S>1){ if(!Mp[S]) ++newcnt, Mp[S]=newcnt+tot; pri[id].push_back(PI(Mp[S], 1)); } } void pre(int E){ for(int i=2; i<=R; i++){ if(!u[i]) Su[++tot]=i; for(int j=1; j<=tot&&i*Su[j]<=R; j++){ u[i*Su[j]]=1;if(!(i%Su[j])) break; } if(i<=E) up=tot; } up=min(239, up); for(int i=1; i<=n; i++) for(int j=1; j<=up; j++) sum[i][j]=sum[i-1][j]+Getc(a[i], Su[j]); } int dis, ans[N]; struct Query{ int x, y, id; bool operator < (const Query &S) const{ if(x/dis!=S.x/dis) return x/dis<S.x/dis; return y<S.y; } void get(int s){x=read(), y=read(), id=s;} }Q[N]; int inv[N], now=1, cnt[3402+N]; inline void add(int x){//乘上 a[x] for(int i=0; i<pri[x].size(); i++){ PI tmp=pri[x][i];int las=cnt[tmp.first]; now=1ll*now*inv[las]%mo*(las+tmp.second)%mo; cnt[tmp.first]+=tmp.second; } } inline void del(int x){//除掉 a[x] for(int i=0; i<pri[x].size(); i++){ PI tmp=pri[x][i];int las=cnt[tmp.first]; now=1ll*now*inv[las]%mo*(las-tmp.second)%mo; cnt[tmp.first]-=tmp.second; } } inline void move(int &l, int &r, int x, int y){ while(l>x) add(--l); while(r<y) add(++r); while(r>y) del(r--); while(l<x) del(l++); return ; } signed main(){ n=read(), m=read();if(!m) return 0;dis=n/sqrt(m); for(int i=1; i<=n; i++) Mx=max(Mx, a[i]=read()); inv[0]=1;for(int i=1; i<N; i++) inv[i]=ksm(i, mo-2);//计算逆元 pre(sqrt(Mx)); for(int i=1; i<=n; i++) Reverse(i);//将 a[i] 中大于 su[239] 的全部丢到一起 for(int i=1; i<=m; i++){ ans[i]=1;Q[i].get(i); for(int j=1; j<=up; j++) ans[i]=1ll*ans[i]*(sum[Q[i].y][j]-sum[Q[i].x-1][j]+1)%mo;//根号分治,先计算小素数的贡献 } for(int i=1; i<=tot+newcnt; i++) cnt[i]=1; sort(Q+1, Q+m+1);int L=1, R=0; for(int i=1; i<=m; i++){ move(L, R, Q[i].x, Q[i].y); ans[Q[i].id]=1ll*ans[Q[i].id]*now%mo; } for(int i=1; i<=m; i++) printf("%d\n", (ans[i]%mo+mo)%mo); return 0; }
常数肥肠大.jpg