1 条题解

  • 0
    @ 2025-8-24 22:12:40

    自动搬运

    查看原文

    来自洛谷,原作者为

    avatar ArrTue
    ac

    搬运于2025-08-24 22:12:40,当前版本为作者最后更新于2021-11-19 12:32:32,作者可能在搬运后再次修改,您可在原文处查看最新版

    自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多

    以下是正文


    update: 修正了错误代码。

    题目传送门

    前置知识:

    欧拉函数,分块。

    题意:

    • 给出 nn 个数 a1na_{1\dots n}qq 次询问,每次给出 l,rl,r,求 $\sum\limits_{i=l}^r\sum\limits_{j=l}^r\gcd(a_i,a_j)$。

    • 保证数列、询问均随机生成,n,q,ai105n,q,a_i\le10^5

    分析:

    $\begin{aligned} &\sum\limits_{i=l}^r\sum\limits_{j=l}^r\gcd(a_i,a_j)\\ =&\sum\limits_{i=l}^r\sum\limits_{j=l}^r\sum\limits_{d\mid a_i\land d\mid a_j}\varphi(d)&(1)\\ =&\sum\limits_{d=1}^{\max(a)}\varphi(d)\sum\limits_{i=l}^r\sum\limits_{j=l}^r[d\mid a_i\land d\mid a_j]\\ =&\sum\limits_{d=1}^{\max(a)}\varphi(d)(\sum\limits_{i=l}^r[d\mid a_i])^2 \end{aligned}$

    其中 (1)(1) 用到了欧拉函数的性质 n=dnφ(d)n=\sum\limits_{d\mid n}\varphi(d)(也可用莫反推出一个新函数 f(d)f(d),实际上 ff 就是 φ\varphi

    官方解法中使用了莫队维护 i=lr[dai]\sum\limits_{i=l}^r[d\mid a_i](不会莫队可以跳过这一段):由于 aia_i 随机生成,故其因数个数期望较少(另一篇题解中认为是一个常数 α\alpha,本题解认为是值域内的总因子个数 vlnvv\ln v 除以值域 vv,即 lnv\ln v),移动莫队指针时可以直接枚举所有 aia_i 的因数 dd 并更新对应的 i=lr[dai]\sum\limits_{i=l}^r[d\mid a_i]。期望复杂度 O(nnlnv)\mathcal O(n\sqrt n\ln v)惊人地可过。

    翻了 AC 代码,发现本题实际上有一种期望 O((n+q)n)\mathcal O((n+q)\sqrt n) 的分块写法。

    首先对值域分块,所有不大于 VVdd 直接暴力求出 [dai][d\mid a_i] 的前缀和,询问时 O(V)\mathcal O(V) 即可处理这一部分的数,总复杂度 O((n+q)V)\mathcal O((n+q)V)

    对于大于 VVdd,由于值比较大,所以随机情况下 aadd 的倍数较少。假设所有为 dd 的倍数的 aaak1,ak2,,akma_{k_1},a_{k_2},\dots,a_{k_m}。当询问左端点 l(kx1,kx]l\in(k_{x-1},k_x],右端点 r[ky,ky+1)r\in[k_y,k_{y+1}) 时,i=lr[dai]\sum\limits_{i=l}^r[d\mid a_i] 即为 yx+1y-x+1;对答案贡献为 φ(d)(yx+1)2\varphi(d)(y-x+1)^2。对于每一个询问,显然不能循环每一个 dd 计算 x,yx,y。但注意到因为 mm 较小,有序对 (x,y)(x,y) 只有 O(m2)\mathcal O(m^2) 种情况,可以从这里入手。

    不妨将询问按右端点排序,对于 yy 相同的询问,dd 的贡献只与左端点有关。而左端点落在一个区间内时,贡献都是一样的。考虑差分,则 dd 只对 y+1y+1 个位置有影响。每次 yy 改变只需修改这几处的值即可。总修改次数为 O(m2)\mathcal O(m^2)

    可以证明 m2m^2 的期望为 O(n2d2)\mathcal O(\dfrac{n^2}{d^2})证明 link)。则对于所有 dd,总修改次数期望为 $\mathcal O(\sum\limits_{d=V}^{\max(a)}\dfrac{n^2}{d^2})=\mathcal O(\dfrac{n^2}V)$。

    对每个询问需要计算出差分数组的前缀和。考虑分块,使修改 O(1)\mathcal O(1),查询 O(n)\mathcal O(\sqrt n)。总时间复杂度 O((n+q)V+n2V+qn)\mathcal O((n+q)V+\dfrac{n^2}V+q\sqrt n),取 V=nV=\sqrt n,复杂度即为 O((n+q)n)\mathcal O((n+q)\sqrt n)

    思路:

    1. 求欧拉函数。

    2. 对值域分块,分别处理。


    具体说明值域分块后大于 VVdd 如何处理。

    max(a)\max(a) 个 vector,存下每个 ddkik_i。考虑右端点所在区间由 [ky0,ky0+1)[k_{y_0},k_{y_0+1})[ky0+1,ky0+2)[k_{y_0+1},k_{y_0+2}),差分数组如何变化:x[1,y0+1]\forall x\in[1,y_0+1],左端点在 (kx1,kx](k_{x-1},k_x] 的贡献由 φ(d)(y0x+1)2\varphi(d)(y_0-x+1)^2 变为 φ(d)(y0x+2)2\varphi(d)(y_0-x+2)^2,增加 φ(d)(2(y0x+1)+1)\varphi(d)(2(y_0-x+1)+1)

    在差分数组上,体现为位置 1 增加 φ(d)(2y0+1)\varphi(d)(2y_0+1),位置 kx+1 (x[1,y0])k_x+1\ (x\in[1,y_0]) 减少 2φ(d)2\varphi(d),位置 ky0+1k_{y_0}+1 减少 φ(d)\varphi(d)

    分块求和,记得处理对应位置块和。


    #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;
    }
    
    • 1

    信息

    ID
    4323
    时间
    1000ms
    内存
    125MiB
    难度
    6
    标签
    递交数
    0
    已通过
    0
    上传者