题解 P5624 [Celeste-A]Black Moonrise 暗夜月升
update: 修正了错误代码。
题目传送门
前置知识:
欧拉函数,分块。
题意:
-
给出
n 个数a_{1\dots n} ,q 次询问,每次给出l,r ,求\sum\limits_{i=l}^r\sum\limits_{j=l}^r\gcd(a_i,a_j) 。 -
保证数列、询问均随机生成,
n,q,a_i\le10^5 。
分析:
其中
官方解法中使用了莫队维护 惊人地可过。
但翻了 AC 代码,发现本题实际上有一种期望
首先对值域分块,所有不大于
对于大于
不妨将询问按右端点排序,对于
可以证明
对每个询问需要计算出差分数组的前缀和。考虑分块,使修改
思路:
-
求欧拉函数。
-
对值域分块,分别处理。
具体说明值域分块后大于
开
在差分数组上,体现为位置 1 增加
分块求和,记得处理对应位置块和。
#include <bits/stdc++.h>
#define rep(i, l, r) for(int i=l, _=r; i<=_; ++i)
typedef long long ll;
using namespace std;
inline int read() {
int res=0; bool k=1; char ch;
while(!isdigit(ch=getchar())) k=ch^45;
do res=(res<<1)+(res<<3)+(ch&15); while(isdigit(ch=getchar()));
return k? res: -res;
}
const int mN=1e5+9, mP=1e4, mL=320;
int n, q, L, V, a[mN], sum[mN][mL];
ll ans[mN];
int pri[mP], cntp, phi[mN];
inline void init(int n) {
phi[1]=1;
for(int i=2, j, u; i<=n; ++i) {
if(!phi[i]) pri[++cntp]=i, phi[i]=i-1;
for(j=1; (u=pri[j]*i)<=n && i/pri[j]*pri[j]^i; ++j) phi[u]=phi[i]*(pri[j]-1);
if(u<=n) phi[u]=phi[i]*pri[j];
}
}
struct Que {int x, y, id;} qn[mN];
bool operator < (Que q1, Que q2) {return q1.y<q2.y;}
ll b[mN], sumb[mL]; //b 为差分数组,sumb 为分块的差分数组
vector<int> num[mN]; //用于存 ki
inline void sol(int x, int p) {
if(x<=V) return ++sum[p][x], void(); //小于 V,直接前缀和
int y0=(int) num[x].size(), t=phi[x];
b[1]+=(ll) (y0<<1|1)*t, sumb[0]+=(ll) (y0<<1|1)*t;
b[p+1]-=t, sumb[(p+1)/L]-=t;
t<<=1;
for(int i=y0-1; i>=0; --i) b[num[x][i]+1]-=t, sumb[(num[x][i]+1)/L]-=t;
num[x].push_back(p);
}
inline ll sqr_(int x) {return (ll) x*x;}
int main() {
init(1e5), n=read(), q=read(), L=sqrt(n), V=sqrt(1e5);
if(L<2) L=2;
rep(i, 1, n) a[i]=read();
rep(i, 1, q) qn[i].x=read(), qn[i].y=read(), qn[i].id=i;
sort(qn+1, qn+q+1);
rep(i, 1, q) {
rep(r, qn[i-1].y+1, qn[i].y) { //扩展 y
memcpy(sum[r], sum[r-1], sizeof sum[r]);
for(int j=1; j*j<=a[r]; ++j) if(a[r]%j==0) {
sol(j, r);
if(j*j^a[r]) sol(a[r]/j, r);
}
}
int x=qn[i].x, y=qn[i].y, id=qn[i].id;
rep(j, 0, x/L-1) ans[id]+=sumb[j]; //大于 V,值域分块
rep(j, x/L*L, x) ans[id]+=b[j];
rep(v, 1, V) ans[id]+=phi[v]*sqr_(sum[y][v]-sum[x-1][v]); //小于 V,直接前缀和差分
}
rep(i, 1, q) printf("%lld\n", ans[i]);
return 0;
}