[国家集训队]Crash的数字表格 / JZPTAB
好像这个题可以低于线性,然后也可以杜教筛到 $O(n^{\frac{2}{3}})$ 这个样子。
然而我不想做,直接线性就过了。。。最后再口胡一下吧。。。
首先暴力推:
$$\begin{aligned}& \sum_{i=1}^{n}\sum_{j=1}^{m} \operatorname{lcm}(i,j)\\ =& \sum_{i=1}^{n}\sum_{j=1}^{m}\dfrac{ij}{\gcd(i,j)}\\ =& \sum_{d=1}\sum_{i=1}^{n}\sum_{j=1}^{m}\dfrac{ij[\gcd(i,j)=d]}{d}\\ =& \sum_{d=1}\sum_{i=1}^{\lfloor\frac{n}{d}\rfloor}\sum_{j=1}^{\lfloor\frac{m}{d}\rfloor}\dfrac{ijd^2}{d}[\gcd(i,j)=1]\\=& \sum_{d=1}d\sum_{i=1}^{\lfloor\frac{n}{d}\rfloor}\sum_{j=1}^{\lfloor\frac{m}{d}\rfloor}ij\sum_{k\mid \gcd(i,j)}\mu(k)\\=& \sum_{d=1}d\sum_{k=1}\mu(k)k^2\sum_{i=1}^{\lfloor\frac{n}{kd}\rfloor}i\sum_{j=1}^{\lfloor\frac{m}{kd}\rfloor}j\end{aligned}$$
设 $h(n)=\sum_{i=1}^{n}i=\dfrac{n(n+1)}{2}$,于是这个就是:
$$\begin{aligned}=& \sum_{d=1}d\sum_{k=1}\mu(k)k^2h(\lfloor\frac{n}{kd}\rfloor)h(\lfloor\frac{m}{kd}\rfloor)\end{aligned}$$
好了,现在我们设 $h’(d)=\sum_{k=1}\mu(k)k^2h(\lfloor\frac{n}{kd}\rfloor)h(\lfloor\frac{m}{kd}\rfloor)$。
$$\begin{aligned}=& \sum_{d=1}dh’(d)\end{aligned}$$
这个显然可以数论分块 $O(\sqrt{n})$ 做,然后 $h’(d)$ 又是 $O(\sqrt{n})$ 数论分块做。加上线性筛之类的东西,这个复杂度就是 $O(n)$ 的了。
这个题已经能做出来了。然而线性还不够 nb,我们还能有低于线性的解法。
首先设 $T=kd$,则 $k=\dfrac{T}{d}$,于是:
$$\begin{aligned}=& \sum_{T=1}\sum_{d\mid T}d\mu(\dfrac{T}{d})(\dfrac{T}{d})^2h(\lfloor\frac{n}{T}\rfloor)h(\lfloor\frac{m}{T}\rfloor)\end{aligned}$$
我们来研究这个比较怪异的卷积,看一看能不能杜教筛一类,先设:
$$f=(\mathbf{id}^2\cdot \mu)*\mathbf{id}$$
然后我们取 $g=\mathbf{id}^2$,然后卷上去:
$$\begin{aligned}(f * g)(n)=& ((\mathbf{id}^2\cdot \mu) * \mathbf{id} * \mathbf{id}^2 )(n)\\ =& ((\mathbf{id}^2\cdot \mu) * \mathbf{id}^2 * \mathbf{id})(n)\\ =& \sum_{d\mid n}((\mathbf{id}^2\cdot \mu) * \mathbf{id}^2)(d)\dfrac{n}{d}\\ =& \sum_{d\mid n}\sum_{k\mid d}k^2\mu(k)(\dfrac{d}{k})^2\dfrac{n}{d}\\ =& n\sum_{d\mid n}d\sum_{k\mid d}\mu(k)\\ =& n\sum_{d\mid n}d[d=1] \\ =& n=\mathbf{id}(n)\end{aligned}$$
这就非常 nb 了,意味着我们有一个:
$$(\mathbf{id}^2\cdot \mu)* \mathbf{id} * \mathbf{id^2}=\mathbf{id}$$
那么 $g$ 和 $f*g$ 的前缀和都非常好求。$g$ 的前缀和随便给一下:
$$S_g(n)=\sum_{i=1}^ng(i)=\dfrac{n(n+1)(2n+1)}{6}$$
然后我们杜教筛直接套就完了。
时间复杂度 $O(n^{\frac{2}{3}})$。
杜教筛不熟的我可以写个式子:
$$g(1)S_f(n)=\sum_{i=1}^n(f*g)(i)-\sum_{y=2}^{n}g(y)S_f(\lfloor\dfrac{n}{y}\rfloor)$$
然后这里的记忆化讲实话确实难搞,所以我直接躺平,用 Hash 随便取个质数模一下就完了。然后这里我取的模数是 $786433$。当然用 map 也是可以的。
因为 $2$ 和 $6$ 都是与 $20101009$ 互质的,所以直接求个逆元。
然而因为我预处理直接躺平,所以最后跑得不如一开始提出的线性算法。
代码(杜教筛 $O(n^{\frac{2}{3}})$):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
   | #include<iostream> #include<cstdio> #include<cstring> #define ll long long using namespace std;
  const ll N=1e7,M=1e6,mo=20101009,inv2=10050505,inv6=16750841,hmo=786433;
  ll n,m,ans,cnt;
  ll prime[M+5],mu[M+5],Sf1[M+5],Sf2[M+5]; bool f[M+5],vis[M+5];
  inline void Init() {   mu[1]=1;f[1]=1;   for(ll i=2;i<=M;i++) {     if(!f[i]) {prime[++cnt]=i;mu[i]=-1;}     for(ll j=1;j<=cnt&&i*prime[j]<=M;j++) {       f[i*prime[j]]=1;       if(i%prime[j]==0) {mu[i*prime[j]]=0;break;}       mu[i*prime[j]]=-mu[i];     }   }   for(ll i=1;i<=M;i++) {     for(ll j=i;j<=M;j+=i) {       Sf1[j]=(Sf1[j]+(mu[i]*i*i+mo)%mo*(j/i)%mo)%mo;     }   }   for(ll i=1;i<=M;i++) Sf1[i]=Sf1[i-1]+Sf1[i]; }
  inline ll H(ll x) {return x%hmo;}
  inline ll S1(ll x) {return x*(x+1)%mo*inv2%mo;} inline ll S2(ll x) {return x*(x+1)%mo*(2*x+1)%mo*inv6%mo;}
  inline ll Sf(ll x) {   if(x<=M) return Sf1[x];ll tmp=H(x);   if(vis[tmp]) return Sf2[tmp];   vis[tmp]=1;Sf2[tmp]=S1(x);   for(ll i=2,j;i<=x;i=j+1) {     j=x/(x/i);     Sf2[tmp]=(Sf2[tmp]-(S2(j)-S2(i-1)+mo)%mo*Sf(x/i)%mo+mo)%mo;   }   return Sf2[tmp]; }
  inline ll read() {   ll ret=0,f=1;char ch=getchar();   while(ch<48||ch>57) {if(ch==45) f=-f;ch=getchar();}   while(ch>=48&&ch<=57) {ret=(ret<<3)+(ret<<1)+ch-48;ch=getchar();}   return ret*f; }
  inline void write(ll x) {   static char buf[22];static ll len=-1;   if(x>=0) {do{buf[++len]=x%10+48;x/=10;}while(x);}   else {putchar(45);do{buf[++len]=-(x%10)+48;x/=10;}while(x);}   while(len>=0) putchar(buf[len--]); }
  int main() {
    n=read();m=read();if(n<m) swap(n,m);   Init();
    for(ll i=1,j;i<=m;i=j+1) {     j=min(n/(n/i),m/(m/i));     ans=(ans+(Sf(j)-Sf(i-1)+mo)*S1(n/i)%mo*S1(m/i)%mo)%mo;   }
    write(ans);
    return 0; }
   | 
 
代码($O(n)$):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
   | #include<iostream> #include<cstdio> #include<cstring> #define ll long long using namespace std;
  const ll N=1e7,mo=20101009;
  ll n,m,ans,cnt;
  ll prime[N+5],mu[N+5],s[N+5]; bool f[N+5];
  inline void Init() {   mu[1]=1;f[1]=1;   for(ll i=2;i<=n;i++) {     if(!f[i]) {prime[++cnt]=i;mu[i]=-1;}     for(ll j=1;j<=cnt&&i*prime[j]<=n;j++) {       f[i*prime[j]]=1;       if(i%prime[j]==0) {mu[i*prime[j]]=0;break;}       mu[i*prime[j]]=-mu[i];     }   }   for(ll i=1;i<=n;i++) {     s[i]=(s[i-1]+mu[i]*i%mo*i%mo)%mo;   } }
  inline ll S(ll x) {return (x*(x+1)/2)%mo;}
  inline ll F(ll x) {   ll res=0;   for(ll i=1,j;i<=m/x;i=j+1) {     j=min((n/x)/(n/x/i),(m/x)/(m/x/i));     res=(res+(s[j]-s[i-1]+mo)*S(n/x/i)%mo*S(m/x/i)%mo)%mo;   }   return res; }
  inline ll read() {   ll ret=0,f=1;char ch=getchar();   while(ch<48||ch>57) {if(ch==45) f=-f;ch=getchar();}   while(ch>=48&&ch<=57) {ret=(ret<<3)+(ret<<1)+ch-48;ch=getchar();}   return ret*f; }
  inline void write(ll x) {   static char buf[22];static ll len=-1;   if(x>=0) {do{buf[++len]=x%10+48;x/=10;}while(x);}   else {putchar(45);do{buf[++len]=-(x%10)+48;x/=10;}while(x);}   while(len>=0) putchar(buf[len--]); }
  inline void writeln(ll x) {write(x);putchar(10);}
  int main() {
    n=read();m=read();   if(n<m) swap(n,m);Init();
    for(ll i=1,j;i<=m;i=j+1) {     j=min(n/(n/i),m/(m/i));     ans=(ans+(S(j)-S(i-1)+mo)*F(i)%mo)%mo;   }
    write(ans);
    return 0; }
   |