problem
计算
1≤n≤1011 1 ≤ n ≤ 10 11
思路
解法一
首先写作 ∑ni=1∑ij=1(n−⌊nij⌋∗ij)=∑ni=1∑ij=1n−∑ni=1∑ij=1⌊nij⌋∗ij ∑ i = 1 n ∑ j = 1 i ( n − ⌊ n i j ⌋ ∗ i j ) = ∑ i = 1 n ∑ j = 1 i n − ∑ i = 1 n ∑ j = 1 i ⌊ n i j ⌋ ∗ i j
对于 ∑ni=1∑ij=1n ∑ i = 1 n ∑ j = 1 i n 不用说了,对于 ∑ni=1∑ij=1⌊nij⌋∗ij=∑ni=1i∗∑ij=1⌊⌊ni⌋j⌋∗j ∑ i = 1 n ∑ j = 1 i ⌊ n i j ⌋ ∗ i j = ∑ i = 1 n i ∗ ∑ j = 1 i ⌊ ⌊ n i ⌋ j ⌋ ∗ j
对于第二维,所求是j∈ [1,i] [ 1 , i ] 我们这里求 [1,n] [ 1 , n ] 由对称性可知 [1,i] [ 1 , i ] 的两倍减去 [i=j] [ i = j ] 的情况即为所求
所以现在目标是 ∑ni=1i∗∑nj=1⌊⌊ni⌋j⌋∗j ∑ i = 1 n i ∗ ∑ j = 1 n ⌊ ⌊ n i ⌋ j ⌋ ∗ j
由于 ⌊ni⌋ ⌊ n i ⌋ 只有 O(n−−√) O ( n ) 种取值,我们枚举 ⌊ni⌋ ⌊ n i ⌋ 的值,对应一段 i i 区间,可知对于这一段,第二维值是相同的。
对于第二维的计算类似,也是分块思想。所以写一个solve函数用于求解 f(n)=∑ni=1⌊ni⌋∗i f ( n ) = ∑ i = 1 n ⌊ n i ⌋ ∗ i 即可。
时间复杂度 O(∑n√i=1ni−−√)=O(n34) O ( ∑ i = 1 n n i ) = O ( n 3 4 ) (只有 O(n−−√) O ( n ) 种取值) 注: ∑nxi=1ni−−√ ∑ i = 1 n x n i 的计算式: n(12+12x) n ( 1 2 + 1 2 x )
TLE 1e11大概要跑37s
解法二
定义 g(n)=f(n)−f(n−1)=∑ni=1i∗(⌊ni⌋−⌊n−1i⌋)=∑d|nd g ( n ) = f ( n ) − f ( n − 1 ) = ∑ i = 1 n i ∗ ( ⌊ n i ⌋ − ⌊ n − 1 i ⌋ ) = ∑ d | n d
对于 g(n) g ( n ) ,即一个数的因子和,我们可以 O(n) O ( n ) 求解出其前 n n 项,对于本题 ,我们处理出 g g 的前项 ,然后求个前缀和就得到了 f(n) f ( n ) ,这一部分的时间复杂度是 O(n23) O ( n 2 3 )
所以对于 ∑ni=1i∗∑nj=1⌊⌊ni⌋j⌋∗j ∑ i = 1 n i ∗ ∑ j = 1 n ⌊ ⌊ n i ⌋ j ⌋ ∗ j ,当 ⌊ni⌋<n23 ⌊ n i ⌋ < n 2 3 时,可以 O(1) O ( 1 ) 得到第二层结果。当 ⌊ni⌋>n23 ⌊ n i ⌋ > n 2 3 时,使用原先的方法求解,这一部分的时间复杂度是 O(∑n13i=1ni−−√)=O(n23) O ( ∑ i = 1 n 1 3 n i ) = O ( n 2 3 )
总时间复杂度 O(n23) O ( n 2 3 ) 1e11大概要跑5s
代码示例
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef __int128 lll;
const int mod=1e9+7;
inline lll cal(lll l,lll r)
{
return (l+r)*(r-l+1)/2;
}
inline lll solve(ll up)//solve \sum_{i=1}^{n} up/i *i;
//显然只有i<=up时有贡献
{
// num++;
// if(num%10000==0) cout<<clock()<<endl;
lll res=0;
for(ll l=1,r;l<=up;l=r+1){
r=up/(up/l);
res=(res+up/l*cal(l,r));
}
return res;
}
inline void write(__int128 x)
{
if(x<0)
{
putchar('-');
x=-x;
}
if(x>9) write(x/10);
putchar(x%10+'0');
}
//lll help1[maxn];//solve f(n/1) f(n/2) f(n/3) f(n/\sqrt(n))
//lll help2[maxn];//solve 1 2 3 \sqrt{n}
const int maxn=21550000;
ll g[maxn];//n^(2/3) g(n)=\sum_{i|n} i
lll f[maxn];//sum_{i=1}^{n} [n/i]*i
int ans[maxn/10];
int help[maxn];//存每个数最小质因子^指数 如12存2^2 18存2^1 32存2^5
bool valid[maxn];
int tot;
void get_prime(int n)
{
memset(valid,true,sizeof(valid));
tot=0;
g[1]=1;help[1]=1;
for(int i=2;i<=n;++i){
if(valid[i]){
ans[++tot]=i;
g[i]=i+1;
help[i]=i;
}
for(int j=1;j<=tot && ans[j]*i<=n;++j){
valid[ans[j]*i]=false;
if(i%ans[j]==0){
help[i*ans[j]]=help[i]*ans[j];
g[i*ans[j]]=g[i]*ans[j]+g[i/help[i]];
break;
}
else{
help[i*ans[j]]=ans[j];
g[i*ans[j]]=g[i]*g[ans[j]];
}
}
}
}
int main()
{
get_prime(maxn);
f[0]=0;
for(int i=1;i<maxn;++i) f[i]=f[i-1]+g[i];
//cout<<clock()<<endl;
//freopen("in.txt","r",stdin);
int t;
cin>>t;
while(t--)
{
ll n;
cin>>n;
lll ans1=0;
for(ll l=1,r;l<=n;l=r+1){
r=n/(n/l);
ll tp=n/l;
if(tp<maxn) ans1+=f[tp]*cal(l,r);
else ans1+=solve(tp)*cal(l,r);
}
lll ans2=0;//i=j
for(ll i=1;i*i<=n;++i){
ll tp=i*i;
ans2+=n/tp*tp;
}
ans1+=ans2;
assert(ans1%2==0);
ans1/=2;
ans1=((lll)n)*n*(n+1)/2-ans1;
write(ans1);
cout<<endl;
}
return 0;
}