BZOJ 2154 + 2693 莫比乌斯反演 (好题

BZOJ 2154题目链接
BZOJ 2693题目链接(权限题)

再送上一道类似推导思路的题:
NJU 1017题目链接

参考文档
(用了一整天 终于刷完文档例题了 qwq)


题意:
给定 n , m ( &lt; = 1 e 7 ) n,m(&lt;=1e7) n,m(<=1e7) ∑ i = 1 n ∑ j = 1 m l c m ( i , j ) \sum_{i =1}^n \sum_{j=1}^m lcm(i,j) i=1nj=1mlcm(i,j)
第一题是单组数据,第二题是多组数据。


思路:

开始写了一个 O ( n n ) O(n\sqrt n) O(nn )的算法 果断T。
下面试着推导一个 O ( n ) O(n) O(n)甚至 O ( n ) O(\sqrt n) O(n )的计算表达式。

设答案为 A n s Ans Ans,由 l c m lcm lcm定义,可得:
A n s = ∑ i = 1 n ∑ j = 1 m i j g c d ( i , j ) Ans = \sum_{i =1}^n \sum_{j=1}^m \frac{ij}{gcd(i,j)} Ans=i=1nj=1mgcd(i,j)ij

但这么直接计算复杂度是 O ( n m ) O(nm) O(nm)严重爆炸
故考虑枚举 g c d ( i , j ) gcd(i,j) gcd(i,j)(设为 g g g)的值,想办法应用莫比乌斯反演进行化简。
A n s = ∑ g = 1 m i n ( n , m ) ( ∑ i = 1 n ∑ j = 1 m i j g ( g c d ( i , j ) = = g ) ) Ans = \sum_{g=1}^{min(n,m)} ( \sum_{i=1}^n\sum_{j=1}^m \frac{ij}{g}(gcd(i,j) == g)) Ans=g=1min(n,m)(i=1nj=1mgij(gcd(i,j)==g))

进一步化简:(转换枚举变量,使 g c d = 1 gcd=1 gcd=1)
A n s = ∑ g = 1 m i n ( n , m ) ( ∑ i = 1 n / g ∑ j = 1 m / g g 2 ∗ i j g ( g c d ( i , j ) = = 1 ) ) Ans = \sum_{g=1}^{min(n,m)} ( \sum_{i=1}^{n/g}\sum_{j=1}^{m/g} \frac{g^2*ij}{g}(gcd(i,j) == 1)) Ans=g=1min(n,m)(i=1n/gj=1m/ggg2ij(gcd(i,j)==1))
得:
A n s = ∑ g = 1 m i n ( n , m ) g ( ∑ i = 1 n / g ∑ j = 1 m / g i j ( g c d ( i , j ) = = 1 ) ) Ans = \sum_{g=1}^{min(n,m)} g( \sum_{i=1}^{n/g}\sum_{j=1}^{m/g} ij(gcd(i,j) == 1)) Ans=g=1min(n,m)g(i=1n/gj=1m/gij(gcd(i,j)==1))

f ( x , y ) = ∑ i = 1 x ∑ j = 1 y i j ( g c d ( i , j ) = = 1 ) f(x,y) = \sum_{i=1}^{x}\sum_{j=1}^{y} ij(gcd(i,j) == 1) f(x,y)=i=1xj=1yij(gcd(i,j)==1)

则:
A n s = ∑ g = 1 m i n ( n , m ) g f ( n g , m g ) Ans = \sum_{g=1}^{min(n,m)} gf(\frac{n}{g},\frac{m}{g}) Ans=g=1min(n,m)gf(gn,gm)

故问题关键便在于如何来求解 f ( x , y ) f(x,y) f(x,y)
此处介绍两种推导方式,一种是利用莫比乌斯函数的性质,另一种则是直接使用变换式。


利用 ∑ d ∣ n μ ( d ) = ( n = = 1 ) \sum_{d|n}\mu(d) = (n==1) dnμ(d)=(n==1)

f ( x , y ) = ∑ i = 1 x ∑ j = 1 y i j ( g c d ( i , j ) = = 1 ) f(x,y) = \sum_{i=1}^{x}\sum_{j=1}^{y} ij(gcd(i,j) == 1) f(x,y)=i=1xj=1yij(gcd(i,j)==1)
g c d ( i , j ) gcd(i,j) gcd(i,j)代入性质表达式中的 n n n,得:
f ( x , y ) = ∑ i = 1 x ∑ j = 1 y i j ∑ d ∣ g c d ( i , j ) μ ( d ) f(x,y) = \sum_{i=1}^{x}\sum_{j=1}^{y} ij \sum_{d|gcd(i,j)}\mu(d) f(x,y)=i=1xj=1yijdgcd(i,j)μ(d)

d ∣ g c d ( i , j ) d|gcd(i,j) dgcd(i,j),故 i , j i,j i,j一定是 d d d的倍数
i = k 1 d   j = k 2 d i= k_1d \space j = k_2d i=k1d j=k2d
转换枚举变量为 d d d,则:
f ( x , y ) = ∑ d = 1 m i n ( x , y ) ∑ k 1 = 1 x d ∑ k 2 = 1 y d k 1 k 2 d 2 μ ( d ) f(x,y) = \sum_{d=1}^{min(x,y)} \sum_{k_1=1}^{\frac{x}{d}}\sum_{k_2=1}^{\frac{y}{d}} k_1k_2d^2\mu(d) f(x,y)=d=1min(x,y)k1=1dxk2=1dyk1k2d2μ(d)
即:
f ( x , y ) = ∑ d = 1 m i n ( x , y ) d 2 μ ( d ) ∑ k 1 = 1 x d ∑ k 2 = 1 y d k 1 k 2 f(x,y) = \sum_{d=1}^{min(x,y)} d^2\mu(d) \sum_{k_1=1}^{\frac{x}{d}}\sum_{k_2=1}^{\frac{y}{d}} k_1k_2 f(x,y)=d=1min(x,y)d2μ(d)k1=1dxk2=1dyk1k2

s u m ( x , y ) = ∑ i = 1 x ∑ j = 1 y i j = ∑ i = 1 x i ∑ j = 1 y j = x ( x + 1 ) 2 y ( y + 1 ) 2 sum(x,y) = \sum_{i=1}^x\sum_{j=1}^y ij =\sum_{i=1}^x i \sum_{j=1}^y j = \frac{x(x+1)}{2}\frac{y(y+1)}{2} sum(x,y)=i=1xj=1yij=i=1xij=1yj=2x(x+1)2y(y+1)


f ( x , y ) = ∑ d = 1 m i n ( x , y ) d 2 μ ( d ) s u m ( x d , y d ) f(x,y) = \sum_{d=1}^{min(x,y)} d^2\mu(d) sum(\frac{x}{d},\frac{y}{d}) f(x,y)=d=1min(x,y)d2μ(d)sum(dx,dy)

故我们可以 O ( n ) O(n) O(n)预处理 d 2 μ ( d ) d^2\mu(d) d2μ(d)的前缀和,然后两次 O ( n ) O(\sqrt n) O(n )的分块除法,总复杂度 O ( n ) O(n) O(n)下解决问题。

下面再说说如何直接利用反演表达式推出此公式。


利用 F ( n ) = ∑ n ∣ d f ( d ) = &gt; f ( n ) = ∑ n ∣ d μ ( d n ) F ( d ) F(n)=\sum_{n|d}f(d) =&gt; f(n) = \sum_{n|d} \mu(\frac{d}{n}) F(d) F(n)=ndf(d)=>f(n)=ndμ(nd)F(d)

f ( x , y ) = ∑ i = 1 x ∑ j = 1 y i j ( g c d ( i , j ) = = 1 ) f(x,y) = \sum_{i=1}^{x}\sum_{j=1}^{y} ij(gcd(i,j) == 1) f(x,y)=i=1xj=1yij(gcd(i,j)==1)
不妨设
g ( n ) = ∑ i = 1 x ∑ j = 1 y i j ( g c d ( i , j ) = = n ) g(n) = \sum_{i=1}^{x}\sum_{j=1}^{y} ij(gcd(i,j) == n) g(n)=i=1xj=1yij(gcd(i,j)==n)
g ( 1 ) = f ( x , y ) g(1) = f(x,y) g(1)=f(x,y)

又设
G ( n ) = ∑ i = 1 x ∑ j = 1 y i j ( n ∣ g c d ( i , j ) ) G(n) = \sum_{i=1}^{x}\sum_{j=1}^{y} ij(n|gcd(i,j)) G(n)=i=1xj=1yij(ngcd(i,j))

故可得:
G ( n ) = ∑ n ∣ d g ( d ) = &gt; g ( n ) = ∑ n ∣ d μ ( d n ) G ( d ) G(n)=\sum_{n|d}g(d) =&gt; g(n) = \sum_{n|d} \mu(\frac{d}{n}) G(d) G(n)=ndg(d)=>g(n)=ndμ(nd)G(d)
所以:
g ( 1 ) = ∑ d = 1 m i n ( x , y ) μ ( d ) G ( d ) g(1) = \sum_{d=1}^{min(x,y)} \mu(d) G(d) g(1)=d=1min(x,y)μ(d)G(d)
故此时的关键在于 G G G 函数的求解

同上第一个推导,设:
s u m ( x , y ) = ∑ i = 1 x ∑ j = 1 y i j = ∑ i = 1 x i ∑ j = 1 y j = x ( x + 1 ) 2 y ( y + 1 ) 2 sum(x,y) = \sum_{i=1}^x\sum_{j=1}^y ij =\sum_{i=1}^x i \sum_{j=1}^y j = \frac{x(x+1)}{2}\frac{y(y+1)}{2} sum(x,y)=i=1xj=1yij=i=1xij=1yj=2x(x+1)2y(y+1)

G G G定义,易得:
G ( d ) = ∑ i = 1 x / d ∑ j = 1 y / d i j ∗ d 2 = d 2 s u m ( x d , y d ) G(d) = \sum_{i=1}^{x/d}\sum_{j=1}^{y/d}ij*d^2 = d^2sum(\frac{x}{d},\frac{y}{d}) G(d)=i=1x/dj=1y/dijd2=d2sum(dx,dy)

综上知:
f ( x , y ) = g ( 1 ) = ∑ d = 1 m i n ( x , y ) d 2 μ ( d ) s u m ( x d , y d ) f(x,y) = g(1) = \sum_{d=1}^{min(x,y)} d^2\mu(d) sum(\frac{x}{d},\frac{y}{d}) f(x,y)=g(1)=d=1min(x,y)d2μ(d)sum(dx,dy)


两种推导方式推出的结果已经足够解决第一题:
代码:

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<algorithm>
using namespace std;
typedef long long ll;

const ll mod = 20101009;
const int A = 1e7 + 10;
bool vis[A];
int pri[A],mu[A],tot;
int n,m;
ll sum[A];

void init(){
    tot = 0;mu[1] = 1;
    for(ll i=2 ;i<=n ;i++){
        if(vis[i] == 0){pri[++tot] = i;mu[i] = -1;}
        for(int j=1 ;j<=tot && i*pri[j] <= n ;j++){
            vis[i*pri[j]] = 1;
            if(i%pri[j] == 0){mu[i*pri[j]] = 0;break;}
            mu[i*pri[j]] = -mu[i];
        }
    }
    for(ll i=1 ;i<=n ;i++) sum[i] = (sum[i-1] + i*i%mod*mu[i]%mod)%mod;
}

ll S(ll x,ll y){
    x %= mod;y %= mod;
    ll res1 = x*(x+1)/2%mod;
    ll res2 = y*(y+1)/2%mod;
    return res1*res2%mod;
}

ll solve(ll x,ll y){
    ll last,res = 0;
    for(ll i=1 ;i<=min(x,y) ;i=last+1){
        last = min(x/(x/i),y/(y/i));
        res = (res + (sum[last] - sum[i-1])*S(x/i,y/i)%mod)%mod;
    }
    if(res<0) res+=mod;
    return res;
}

int main(){
    scanf("%d%d",&n,&m);if(n>m) swap(n,m);
    init();
    ll last,ans = 0;
    for(ll i=1 ;i<=n ;i=last+1){
        last = min(n/(n/i),m/(m/i));
        ans = (ans + (i+last)*(last-i+1)/2%mod*solve(n/i,m/i)%mod)%mod;
    }
    if(ans < 0) ans += mod;
    printf("%lld\n",ans);
}

但对于有多组输入的第二题, O ( n ) O(n) O(n)的复杂度还是不能在时限内得出答案。
让我们再仔细观察一下最终的结果表达式:

A n s = ∑ g = 1 m i n ( n , m ) g ∑ d = 1 m i n ( n g , m g ) d 2 μ ( d ) s u m ( n d g , m d g ) Ans = \sum_{g=1}^{min(n,m)}g \sum_{d=1}^{min(\frac{n}{g},\frac{m}{g})}d^2\mu(d)sum(\frac{n}{dg},\frac{m}{dg}) Ans=g=1min(n,m)gd=1min(gn,gm)d2μ(d)sum(dgn,dgm)

D = d g D = dg D=dg我们再一次转化枚举变量。
A n s = ∑ D = 1 m i n ( n , m ) s u m ( n D , m D ) ∑ d ∣ D d 2 μ ( d ) D d Ans = \sum_{D=1}^{min(n,m)}sum(\frac{n}{D},\frac{m}{D}) \sum_{d|D}d^2\mu(d)\frac{D}{d} Ans=D=1min(n,m)sum(Dn,Dm)dDd2μ(d)dD

易发现,后面一个求和的表达式是一个可以线性筛出的积性函数。
故我们可以 O ( n ) O(n) O(n)预处理,然后每组数据 O ( n ) O(\sqrt n) O(n )得出答案。
至此 该题大功告成。

同样该转化式也可以用在优化第一题:
代码:(第一题 BZOJ 2154)

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<algorithm>
using namespace std;
typedef long long ll;
 
const ll mod = 20101009;
const int A = 1e7 + 10;
bool vis[A];
int pri[A],tot;
ll sum[A],h[A];
int n,m;
 
void init(){
    tot = 0;h[1] = 1;
    for(ll i=2 ;i<=n ;i++){
        if(vis[i] == 0){pri[++tot] = i;h[i] =(i-1LL*i*i)%mod;}
        for(int j=1 ;j<=tot && i*pri[j] <= n ;j++){
            vis[i*pri[j]] = 1;
            if(i%pri[j] == 0){h[i*pri[j]] = 1LL*pri[j]*h[i]%mod;break;}
            h[i*pri[j]] = h[pri[j]]*h[i]%mod;
        }
    }
    for(int i=1 ;i<=n ;i++) sum[i] = ((h[i] + sum[i-1])%mod + mod)%mod;
}
 
ll S(ll n,ll m){
    n %= mod;m %= mod;
    return ((n*(n+1)/2)%mod) * ((m*(m+1)/2)%mod)%mod;
}
 
int main(){
    scanf("%d%d",&n,&m);
    if(n>m) swap(n,m);
    init();
    ll ans = 0;
    int last;
    if(n>m) swap(n,m);
    for(int i=1 ;i<=n ;i=last+1){
        last = min(n/(n/i),m/(m/i));
        ans = (ans + S(n/i,m/i)*(sum[last] - sum[i-1])%mod)%mod;
    }
    printf("%lld\n",(ans%mod+mod)%mod);
    return 0;
}

代码:(第二题 BZOJ 2693)

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<algorithm>
using namespace std;
typedef long long ll;
 
const ll mod = 100000009;
const int A = 1e7 + 10;
bool vis[A];
int pri[A],tot;
ll sum[A],h[A];
 
void init(){
    tot = 0;h[1] = 1;
    for(ll i=2 ;i<A ;i++){
        if(vis[i] == 0){pri[++tot] = i;h[i] =(i-1LL*i*i)%mod;}
        for(int j=1 ;j<=tot && i*pri[j] < A ;j++){
            vis[i*pri[j]] = 1;
            if(i%pri[j] == 0){h[i*pri[j]] = 1LL*pri[j]*h[i]%mod;break;}
            h[i*pri[j]] = h[pri[j]]*h[i]%mod;
        }
    }
    for(int i=1 ;i<A ;i++) sum[i] = ((h[i] + sum[i-1])%mod + mod)%mod;
}
 
ll S(ll n,ll m){
    n %= mod;m %= mod;
    return ((n*(n+1)/2)%mod) * ((m*(m+1)/2)%mod)%mod;
}
 
int main(){
    init();
    int T;
    scanf("%d",&T);
    while(T--){
        int n,m;
        scanf("%d%d",&n,&m);
        ll ans = 0;
        int last;
        if(n>m) swap(n,m);
        for(int i=1 ;i<=n ;i=last+1){
            last = min(n/(n/i),m/(m/i));
            ans = (ans + S(n/i,m/i)*(sum[last] - sum[i-1])%mod)%mod;
        }
        printf("%lld\n",(ans%mod+mod)%mod);
    }
    return 0;
}
### 回答1: bzoj作为一个计算机竞赛的在线评测系统,不仅可以提供大量的题目供程序员练习和学习,还可以帮助程序员提升算法和编程能力。为了更好地利用bzoj进行题目的学习和刷题,制定一个bzoj做题计划是非常有必要的。 首先,我们需要合理安排时间,每天留出一定的时间来做bzoj的题目。可以根据自己的时间安排,每天挑选适量的题目进行解答。可以先从难度较低的题目开始,逐渐提高难度,这样既能巩固基础知识,又能挑战自己的思维能力。 其次,要有一个计划和目标。可以规划一个每周或每月的题目数量目标,以及每个阶段要学习和掌握的算法知识点。可以根据bzoj的题目分类,如动态规划、图论、贪心算法等,结合自己的实际情况,有针对性地选择题目进行学习。 此外,要充分利用bzoj提供的资源。bzoj网站上有很多高质量的题解和优秀的解题代码,可以参考和学习。还有相关的讨论区,可以与其他程序员交流和讨论,共同进步。 最后,要坚持并保持思考。做题不是单纯为了刷数量,更重要的是学会思考和总结。遇到难题时,要有耐心,多思考,多尝试不同的解法。即使不能一次性解出来,也要学会思考和分析解题过程,以及可能出现的错误和优化。 总之,bzoj做题计划的关键在于合理安排时间、制定目标、利用资源、坚持思考。通过有计划的刷题,可以提高算法和编程能力,并培养解决问题的思维习惯,在计算机竞赛中取得更好的成绩。 ### 回答2: bzoj做题计划是指在bzoj这个在线测评系统上制定一套学习和刷题的计划,并且将计划记录在excel表格中。该计划主要包括以下几个方面的内容。 首先是学习目标的设定。通过分析自己的水平和知识缺口,可以设定一个合理的目标,比如每天解决一定数量的题目或者提高特定的算法掌握程度。 其次是题目选择的策略。在excel表格中可以记录下自己选择的题目编号、题目类型和难度等信息。可以根据题目的类型和难度来安排每天的刷题计划,确保自己可以逐步提高技巧和解题能力。 然后是学习进度的记录和管理。将每天的完成情况记录在excel表格中,可以清晰地看到自己的学习进度和任务完成情况。可以使用图表等功能来对学习进度进行可视化展示,更好地管理自己的学习计划。 同时,可以在excel表格的备注栏中记录下每道题目的解题思路、关键点和需要复习的知识点等信息。这样可以方便自己回顾和总结,巩固所学的知识。 最后,可以将excel表格与其他相关资料进行整合,比如算法教材、题目解析和学习笔记等。这样可以形成一个完整的学习档案,方便自己进行系统的学习和复习。 总之,bzoj做题计划excel的制定和记录可以帮助我们更加有条理和高效地进行学习和刷题。通过合理安排学习目标和题目选择策略,记录学习进度和思路,并整合其他学习资料,我们可以提高自己的解题能力,并在bzoj上取得更好的成绩。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值