2 条题解
-
0
upd: (与 zky 讨论)其实预处理部分可以 fft 然后修误差,这样就能做到 了。
uupd:其实是 zky 的 subset。
- 参考资料:《数论选讲》in APIO2024 by zhoukangyang.
- Notations: 为 的上界;记前缀和 。
讲义里提到本题的 做法,但没有细说。我将首先详细地陈述此做法,然后提出一个 的做法。
-
多元积性函数
函数 满足当 时,有 ,则称 为(三元)积性函数。
-
Observation:令 ,则 是积性函数。问题转化为三元积性函数的矩阵和。
-
积性分解
将 分解为 ,,,对于积性函数
-
狄利克雷卷积
三元积性函数的狄利克雷卷积定义为
$$(f*g)(x,y,z)=\sum_{a|x}\sum_{b|y}\sum_{c|z}f(a,b,c)g(x/a,y/b,z/c)$$类似一元的情况,有如下性质成立:
- 积性函数的卷积是积性函数。
- 积性函数的逆是积性函数。
-
贝尔级数
在素数 处观测积性函数 ,定义贝尔级数为三元形式幂级数
$$\mathcal F_p(u_1,u_2,u_3)=\sum_{i=0}^{+\infty}\sum_{j=0}^{+\infty}\sum_{k=0}^{+\infty}f(p^i,p^j,p^k)u_1^iu_2^ju_3^k$$类似一元的情况,两个函数卷积,对应的贝尔级数相乘。
-
低阶拟合法
模仿 Powerful Number 的思路,构造一个数论函数 使得 在素数幂次较低时相等,然后做除法 , 的非零元素有希望较少。
计算 的矩阵和时
$$\begin{aligned} S_f(A,B,C)&=\sum_{i=1}^A\sum_{j=1}^B\sum_{k=1}^Cf(i,j,k)\\ &=\sum_{i=1}^A\sum_{j=1}^B\sum_{k=1}^C\sum_{x|i}\sum_{y|j}\sum_{z|j}h(x,y,z)g(i/x,j/y,k/z)\\ &=\sum_{x=1}^A\sum_{y=1}^B\sum_{z=1}^Ch(x,y,z)\sum_{i=1}^{\lfloor A/x\rfloor}\sum_{j=1}^{\lfloor B/y\rfloor}\sum_{j=1}^{\lfloor C/z\rfloor}g(i,j)\\ &=\sum_{x=1}^A\sum_{y=1}^B\sum_{z=1}^Ch(x,y,z)S_g(\lfloor A/x\rfloor,\lfloor B/y\rfloor,\lfloor C/z\rfloor)\\ \end{aligned}$$如果 稀疏,且 的矩阵和易求,我们就得到了一个不错的算法。
一个自然的想法是,构造 ,则
- 容易计算。$$\begin{aligned} S_g(A,B,C)&=\sum_{i=1}^A\sum_{j=1}^B\sum_{k=1}^Cd(i)d(j)d(k)\\ &=\left(\sum_{i=1}^Ad(i)\right)\left(\sum_{j=1}^Bd(j)\right)\left(\sum_{k=1}^Cd(k)\right)\\ &=S_d(A)S_d(B)S_d(C) \end{aligned}$$
预处理 后容易 计算。
-
和 在低阶处相等。
可以发现 ,,。
为了更直观地说明这一点,构造贝尔级数。
$$\begin{matrix} \begin{aligned} f(p^i,p^j,p^k)&=d(p^{i+j+k})=i+j+k+1\\ \mathcal F_p(u_1,u_2,u_3)&=\sum_{i=0}^{+\infty}\sum_{j=0}^{+\infty}\sum_{k=0}^{+\infty}f(p^i,p^j,p^k)u_1^iu_2^ju_3^k\\ &=\sum_{i=0}^{+\infty}\sum_{j=0}^{+\infty}\sum_{k=0}^{+\infty}(i+j+k+1)u_1^iu_2^ju_3^k \end{aligned}\\\\ \begin{aligned} g(p^i,p^j,p^k)&=d(p^i)d(p^j)d(p^k)=(i+1)(j+1)(k+1)\\ \mathcal G_p(u_1,u_2,u_3)&=\sum_{i=0}^{+\infty}\sum_{j=0}^{+\infty}\sum_{k=0}^{+\infty}g(p^i,p^j,p^k)u_1^iu_2^ju_3^k\\ &=\sum_{i=0}^{+\infty}\sum_{j=0}^{+\infty}\sum_{k=0}^{+\infty}(i+1)(j+1)(k+1)u_1^iu_2^ju_3^k \end{aligned}\\\\ \mathcal H_p=\mathcal F_p/\mathcal G_p \end{matrix}$$和 的系数在“三条棱”上是相等的,根据幂级数逆元的知识,可以发现 在这三条棱上都是 ,没有更多的项。即是说:
不仅如此,计算发现:
$$\begin{matrix} h(1,1,p)=h(1,p,1)=h(p,1,1)=-1\\ h(1,1,p^2)=h(1,p^2,1)=h(p^2,1,1)=2 \end{matrix}$$除了上述项,其余都是零。
-
密度的分析
枚举量就是 的非零项数量,将条件放宽为 ,。
记 为满足 的非零 个数,则复杂度为 。
可以证明 是积性函数,且 ,。
- Lemma: 对于数论函数 ,找出最小的 满足 。记 为 的最大值,其余 ,则 。
根据这个引理,可以得到 ,对应复杂度 。
此界太过宽松,这是因为我们将矩形区域粗暴地放缩成了双曲区域 ,两者的面积比就是 。为了去除多余的 log 因子,必须考虑为矩形区域。
定义三元数论函数 ,我们的目标是估计 的矩阵和。
令 满足 ,其余都是 ,。显然,。
先考察 的矩阵和, 总是可以写作 ,枚举 进行计数:
$$\begin{aligned} S_{v_1}(A,B,C)&=\sum_{x=1}^{+\infty}\sum_{y=1}^{+\infty}\sum_{z=1}^{+\infty}[x\perp y][x\perp z][y\perp z][xy\leq A][xz\leq B][yz\leq C]\\ &\leq \sum_{x=1}^{+\infty}\sum_{y=1}^{+\infty}\sum_{z=1}^{+\infty}[xy\leq A][xz\leq B][yz\leq C]\\ &\sim\sum_{x=1}^{+\infty}\sum_{y=1}^{+\infty}[xy\leq A]\min(B/x,C/y)\\ \end{aligned}$$用讨论将 拆掉, 较小的部分是
$$\begin{aligned} &\sim C\sum_{x=1}^{\sqrt{AB/C}}\sum_{Cx/B\leq y\leq A/x}^{+\infty}1/y\\ &\sim C\sum_{x=1}^{\sqrt{AB/C}}\ln(AB/Cx^2)\\ &\sim C\cdot T\Big(\sqrt{AB/C}\Big) \end{aligned}$$根据对称性,另一部分是 。
其中 ,这是一个经典形式
$$\begin{aligned} T(2n)&=\sum_{i=1}^{2n}\ln(2n/i)\\ &=\sum_{i=1}^{n}\big(\ln 2+\ln(n/i)\big)+\sum_{i=n+1}^{2n}\ln(2n/i)\\ &=(\ln2)\cdot 2n+T(n)\\ &=O(n)+T(n) \end{aligned}$$解得 。代入得 。
考虑 的矩阵和
$$\begin{aligned} S_{v}(N,N,N)&=\sum_{x,y,z\leq N}v_2(x,y,z)S_{v_1}(N/x,N/y,N/z)\\ &=\sum_{x,y,z\leq N}v_2(x,y,z)\sqrt{\dfrac{N^3}{xyz}} \end{aligned}$$记 为满足 的非零 个数。 的全零前缀更长,根据引理, 在 的非零个数不超过 ,密度可以估计为 。枚举 ,复杂度可以估计为
$$\begin{aligned} \sum_{T\leq N^3}\sqrt{\dfrac{N^3}{T}}s_2(T)&=\sum_{T\leq N^3}\sqrt{\dfrac{N^3}{T}}\tilde O(T^{-2/3})\\ &= N^{3/2}\sum_{T\leq N^3}\tilde O(T^{-7/6})\\ &= O(N^{3/2}) \end{aligned}$$综上,我们证明了枚举量是 。
代码如下:
#include<cstdio> #define MaxN 100050 using namespace std; const int mod=1000000007; int p[MaxN>>3],tn,d[MaxN]; void sieve(int n) { d[1]=1; for (int i=2;i<=n;i++) { if (!d[i]) d[p[++tn]=i]=2; for (int j=1,t;j<=tn&&(t=i*p[j])<=n;j++) { if (i%p[j]==0) {d[t]=2*d[i]-d[i/p[j]];break;} d[t]=2*d[i]; } } for (int i=1;i<=n;i++) d[i]+=d[i-1]; } int A,B,C,ret; void dfs(int x, int y, int z, int k, int now) { ret=(ret+1ll*now*d[A/x]*d[B/y]%mod*d[C/z])%mod; for (int j=k;j<=tn;j++) { int flag=(1ll*x*p[j]<=A)|((1ll*y*p[j]<=B)<<1)|((1ll*z*p[j]<=C)<<2); if (flag<=2||flag==4)break; if ((flag&3)==3) dfs(x*p[j],y*p[j],z,j+1,-now); if ((flag&5)==5) dfs(x*p[j],y,z*p[j],j+1,-now); if ((flag&6)==6) dfs(x,y*p[j],z*p[j],j+1,-now); if (flag==7) dfs(x*p[j],y*p[j],z*p[j],j+1,2*now); } } void solve() { ret=0; dfs(1,1,1,1,1); printf("%d\n",(ret+mod)%mod); } int main() { sieve(100000); int T; scanf("%d",&T); while(T--){ scanf("%d%d%d",&A,&B,&C); solve(); } return 0; }-
进一步拟合
令 (实际上等于 ),其余为零。令 ,
$$\begin{matrix} \begin{aligned} S_f(A,B,C)=\sum_{x=1}^A\sum_{y=1}^B\sum_{z=1}^Ch_2(x,y,z)S_{q*g}(\lfloor A/x\rfloor,\lfloor B/y\rfloor,\lfloor C/z\rfloor) \end{aligned}\\\\ \begin{aligned} S_{q*g}(A,B,C)&=\sum_{x,y}q(xy,x,y)S_{g}(\lfloor A/xy\rfloor,\lfloor B/x\rfloor,\lfloor C/y\rfloor )\\ &=\sum_{x,y}\mu(x)\mu(y)S_d(\lfloor A/xy\rfloor)S_d(\lfloor B/x\rfloor)S_d(\lfloor C/y\rfloor)\\ &=\sum_{x}\mu(x)S_d(\lfloor B/x\rfloor)\sum_{y}\mu(y)S_d(\lfloor A/xy\rfloor)S_F(\lfloor C/y\rfloor)\\ &=\sum_{x}\mu(x)S_d(\lfloor B/x\rfloor)W(\lfloor A/x\rfloor,C)\\ \end{aligned} \end{matrix}$$其中 $W(A,C)=\sum_{y}\mu(y)S_d(\lfloor A/y\rfloor)S_d(\lfloor C/y\rfloor)$,有用的 总是整除位置,状态量 。
可以整除分块求解。复杂度 。如此求解所有需要的 ,复杂度是
$$\sum_{x=1}^{\sqrt{N}}\sum_{y=1}^{\sqrt{N}}\sqrt{N/x}+\sqrt{N/y}\sim N\left(\sum_{x=1}^{\sqrt{N}}x^{-1/2}\right)=O(N^{5/4})$$预处理 之后, 可以用 枚举计算(甚至不需要整除分块)。根据对称性,,所以复杂度可以做到 。
-
密度的分析
在除以 之后,进一步使得 变成了零,只有 仍然非零。
对于 首先枚举可行的 ,再确定 的方案数。
类似讲义中二元的情况, 的每个素数幂必须同时为 或同时非零。
记 为满足 的合法 对子数, 是奇函数且 。
将多项式打表可以发现, 则 ,这说明 则 。对于一个确定的 , 的方案数不超过 。于是我们关心的是 的前缀和。
满足 ,根据引理,。实际上 ,非零 在 中的数目为 。
-
对每个 计算 的开销
$$\begin{aligned} &\sum_{x=1}^A\sum_{y=1}^B\sum_{z=1}^C[h_2(x,y,z)\neq 0]\min(B/y,C/z)\\ &=\sum_{y=1}^B\sum_{z=1}^C\min(B/y,C/z)\sum_{x=1}^A[h_2(x,y,z)\neq 0]\\ &\leq \sum_{y=1}^B\sum_{z=1}^C\min(B/y,C/z)(s\cdot d')(yz)\\ &=\sum_{y=1}^B\sum_{z=1}^C(s\cdot d')(yz)\sum_{t\leq B/y,C/z}1\\ &=\sum_{t=1}^{\min(B,C)}\sum_{y=1}^{B/t}\sum_{z=1}^{C/t}(s\cdot d')(yz)\\ &\leq \sum_{t=1}^{\min(B,C)}S_{s\cdot d'}(BC/t^2)\\ &\sim \sum_{t=1}^{\min(B,C)}\dfrac{\sqrt{BC}}{t^2}\log^2N\\ &\sim N\log^3N\\ \end{aligned}$$
综上,我们证明了算法的复杂度为 ,代码如下:
#include<algorithm> #include<cstring> #include<cstdio> #include<cmath> #define MaxN 100050 #define MaxS 320 using namespace std; const int mod=1000000007, K=17; int p[MaxN>>3],tn,pk[MaxN>>3][K+1], d[MaxN],mu[MaxN],smu[MaxN], H[K+1][K+1][K+1]; void sieve(int n) { d[1]=mu[1]=1; for (int i=2;i<=n;i++) { if (!d[i]) { d[p[++tn]=i]=2; mu[i]=-1; pk[tn][0]=1; for (int j=1;;j++){ if (1ll*pk[tn][j-1]*i>n)break; pk[tn][j]=pk[tn][j-1]*i; } } for (int j=1,t;j<=tn&&(t=i*p[j])<=n;j++) { if (i%p[j]==0) {d[t]=2*d[i]-d[i/p[j]];break;} d[t]=2*d[i]; mu[t]=-mu[i]; } } for (int i=1;i<=n;i++) { d[i]+=d[i-1]; smu[i]=smu[i-1]+mu[i]; } H[0][0][0]=1; H[1][1][0]=H[1][0][1]=H[0][1][1]=-1; H[1][1][1]=2; for (int i=1;i<=K;i++) for (int j=0;j<=K;j++) for (int k=0;k<=K;k++) { if (j>0) H[i][j][k]+=H[i-1][j-1][k]; if (k>0) H[i][j][k]+=H[i-1][j][k-1]; if (i>1&&j>0&&k>0) H[i][j][k]-=H[i-2][j-1][k-1]; } } int getk(int x, int p){ int ret=0; while(x>=p){x/=p;ret++;} return ret; } int calcS0(int A, int C) { if (A<C)swap(A,C); int ret=0,l=1,r; while(l*l<=A){ if (mu[l]) ret=(ret+1ll*mu[l]*d[A/l]*d[C/l])%mod; l++; } for (;l<=C;l=r+1){ r=min(A/(A/l),C/(C/l)); ret=(ret+1ll*(smu[r]-smu[l-1])*d[A/l]%mod*d[C/l])%mod; } return ret; } int SAB[MaxS*2][MaxS*2],SAC[MaxS*2][MaxS*2], savA,savB,savC,sqrtA,sqrtB,sqrtC; inline int getSAB(int A, int B) { int i=(A<=sqrtA) ? A+MaxS : savA/A, j=(B<=sqrtB) ? B+MaxS : savB/B; return SAB[i][j] ? SAB[i][j] : SAB[i][j]=calcS0(A,B); } inline int getSAC(int A, int C) { int i=(A<=sqrtA) ? A+MaxS : savA/A, j=(C<=sqrtC) ? C+MaxS : savC/C; return SAC[i][j] ? SAC[i][j] : SAC[i][j]=calcS0(A,C); } int S(int A, int B, int C) { int ret=0; if (A<C||B<C) { int lim=min(A,B); for (int x=1;x<=lim;x++) if (mu[x]) ret=(ret+1ll*mu[x]*d[B/x]*getSAC(A/x,C))%mod; return ret; } for (int x=1;x<=C;x++) if (mu[x]) ret=(ret+1ll*mu[x]*d[C/x]*getSAB(A/x,B))%mod; return ret; } int ret; void dfs(int a, int b, int c, int k, int now) { ret=(ret+1ll*now*S(a,b,c))%mod; for (int j=k;j<=tn;j++) { if (b<p[j]||c<p[j])break; int ka=getk(a,p[j]), kb=getk(b,p[j]), kc=getk(c,p[j]); for (int x=0;x<=ka;x++) for (int y=1;y<=kb&&y<=x+1;y++) for (int z=max(x-y,1);z<=kc&&y+z<=x+2;z++) dfs(a/pk[j][x], b/pk[j][y], c/pk[j][z], j+1, 1ll*now*H[x][y][z]%mod); } } void solve(int A, int B, int C) { ret=0; savA=A;sqrtA=sqrt(A); savB=B;sqrtB=sqrt(B); savC=C;sqrtC=sqrt(C); memset(&SAB[0][0],0,sizeof(SAB)); memset(&SAC[0][0],0,sizeof(SAC)); dfs(A,B,C,1,1); printf("%d\n",(ret+mod)%mod); } int main() { int T,A,B,C; sieve(100000); scanf("%d",&T); while(T--){ scanf("%d%d%d",&A,&B,&C); solve(A,B,C); } return 0; }两个算法的运行时间如下图所示。由于常数较大,新算法在本题的数据范围下并无优势。

-
0
蒟蒻语
第 50 道黑题,写篇题解祭一下 /cy
蒟蒻解
$\begin{aligned} &\sum\limits_{i=1}^{A} \sum\limits_{j=1}^{B} \sum\limits_{k=1}^{C} d(i, j, k)\\ &= \sum\limits_{i=1}^{A} \sum\limits_{j=1}^{B} \sum\limits_{k=1}^{C} \sum\limits_{x|i} \sum\limits_{y|j} \sum\limits_{z|k} [\gcd(x, y) = 1][\gcd(y, z) = 1][\gcd(z, x) = 1]\\ &= \sum\limits_{x=1}^{A} \sum\limits_{y=1}^{B} \sum\limits_{z=1}^{C} [\gcd(x, y) = 1][\gcd(y, z) = 1][\gcd(z, x) = 1] \left\lfloor\frac{A}{x}\right\rfloor \left\lfloor\frac{B}{y}\right\rfloor \left\lfloor\frac{C}{z}\right\rfloor\\ &= \sum\limits_{d_1=1}^{A} \mu(d_1)\sum\limits_{d_2=1}^{B} \mu(d_2) \sum\limits_{d_3=1}^{C} \mu(d_3) \sum\limits_{x=1}^{ \left\lfloor\frac{A}{\operatorname{lcm}(d_1,d_2)}\right\rfloor} \left\lfloor\frac{A}{\operatorname{lcm}(d_1,d_2)x}\right\rfloor \sum\limits_{y=1}^{ \left\lfloor\frac{B}{\operatorname{lcm}(d_2,d_3)}\right\rfloor} \left\lfloor\frac{B}{\operatorname{lcm}(d_2,d_3)y}\right\rfloor \sum\limits_{z=1}^{ \left\lfloor\frac{C}{\operatorname{lcm}(d_3,d_1)}\right\rfloor} \left\lfloor\frac{C}{\operatorname{lcm}(d_3,d_1)z}\right\rfloor\\ \end{aligned}$
(设 )
考虑哪些三元组会有贡献。
- 不为 。
- $\operatorname{lcm}(d_1,d_2) , \operatorname{lcm}(d_2,d_3), \operatorname{lcm}(d_3,d_1) \le Max$
把一个数看成一个点。
首先我们把 为 的点排除在外。
然后把 的点连接起来。
然后再做一遍三元环计数就好了,暴力枚举每一个三元环统计答案。为了更快地统计答案,可以先把边定向。
这样的时间复杂度是 的。( 是边数)
事实上,边数最多只有 条 (用蒟蒻劣质的程序算出来)
因此这道题就被解决了
蒟蒻码
#include<bits/stdc++.h> #define L(i, j, k) for(int i = j; i <= k; i++) #define R(i, j, k) for(int i = j; i >= k; i--) #define ll long long using namespace std; const int N = 1e5 + 7; const int M = 1e6 + 7; const int mod = 1e9 + 7; bool Prime[N]; int tot, P[N >> 3], mu[N]; void xxs() { mu[1] = 1; L(i, 2, 1e5) { if(!Prime[i]) P[++tot] = i, mu[i] = -1; for(int j = 1; P[j] * i <= 1e5 && j <= tot; j++) { Prime[P[j] * i] = 1; if(i % P[j] == 0) { mu[P[j] * i] = 0; break; } mu[P[j] * i] = -mu[i]; } } } vector<int> ve[N]; unordered_map<int, bool> mp[N]; int T, Max, A, B, C, ans, p[N]; int sA[M], sB[M], sC[M], cnt, deg[N]; int gcd(int x, int y) { return x == 0 ? y : gcd(y % x, x); } ll lcm(int x, int y) { return 1ll * x / gcd(x, y) * y; } int ta, tb, tc, sum; void dh(int a, int b, int c) { (sum += 1ll * p[a / ta] * p[b / tb] % mod * p[c / tc] % mod) %= mod; } void get(int a, int b, int c) { sum = 0; if(a == b && b == c) dh(A, B, C); else if(a == b || b == c || c == a) dh(A, B, C), dh(C, A, B), dh(B, C, A); else dh(A, B, C), dh(A, C, B), dh(B, A, C), dh(B, C, A), dh(C, A, B), dh(C, B, A); (ans += (mu[a] * mu[b] * mu[c] * sum % mod + mod) % mod) %= mod; } int vis[N]; void js() { L(i, 1, cnt) { int &u = sA[i], &v = sB[i]; if(deg[u] > deg[v]) swap(u, v); else if(deg[u] == deg[v] && u > v) swap(u, v); ve[u].push_back(i); } L(i, 1, Max) { for(int j : ve[i]) vis[sB[j]] = sC[j]; for(int j : ve[i]) for(int k : ve[sB[j]]) if(vis[sB[k]]) ta = vis[sB[k]], tb = sC[j], tc = sC[k], get(i, sB[j], sB[k]); for(int j : ve[i]) vis[sB[j]] = 0; } } void getans(int x) { for(int l = 1, r; l <= x; l = r + 1) { r = (x / (x / l)); (p[x] += 1ll * (r - l + 1) * (x / l) % mod) %= mod; } } int main() { scanf("%d", &T); xxs(); L(i, 1, 1e5) getans(i); while(T--) { scanf("%d%d%d", &A, &B, &C); ans = cnt = 0; Max = max(max(A, B), C); R(i, Max, 1) for(int j = i; j <= Max; j += i) { if(!mu[j]) continue; for(int k = j; k <= 1ll * Max * i / j; k += i) { if(!mu[k]) continue; if(!mp[j][k]) ++cnt, sA[cnt] = j, sB[cnt] = k, sC[cnt] = j / i * k, deg[j] ++, deg[k] ++, mp[j][k] = 1; } } js(); L(i, 1, Max) ve[i].clear(), mp[i].clear(), deg[i] = 0; printf("%d\n", (ans + mod) % mod); } return 0; }
- 1
信息
- ID
- 2374
- 时间
- 5000ms
- 内存
- 512MiB
- 难度
- 10
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者