JZOJ 5843.B

Description

给定 n 个正整数序列 ,每个序列长度为 m
选择至少 1 个序列,在每个被选择的序列中选择一个元素,求出所有被选择的元素的 gcd
求所有方案的结果之和,答案对 1e9+7 取模。两种方案不同,当且仅当存在至少一个元素,在一种方案中被选择,在另一种中没有。

Input

第一行,两个正整数 nm
接下来 n 行,每行 m 个正整数,第 i 行代表序列 。

Output

第一行,一个整数,代表答案对 1e9+7 取模的结果。

解析

一道比较难的莫比乌斯反演题,要用到其中的性质,且按套路行事技巧处很多
最后推出的是一个关于欧拉函数的式子,莫比乌斯不见了

好,现在进行套路推导
先设 f(x) 表示选择至少一个序列,在每个被选择的序列选择一个元素,它们的 gcd=x 的方案数。
则易得

f(x)=i=1n(j=1m[ai,j=x]+1)1

然后套路 F(x) 表示同 f(x) 但涵盖了 x 的倍数,即

F(x)=x|df(d)=x|d(i=1n(j=1m[ai,j=d]+1)1)

然后我们发现,我们先枚举 x ,再枚举其倍数 d,而后面 [ai,j=d] 肯定是 x 的倍数,所以我们可以简化式子

x|d(i=1n(j=1m[ai,j=d]+1)1)=i=1(j=1m[x|ai,j]+1)1

而此时,为了日后式子的简便即实现,我们设
cnti,x=j=1m[x|ai,j] 表示第 i 个数列所有是 x 的倍数的数的个数
再为了枚举得到所有答案,我们设 lim 表示所有元素的最大值
然后一波推式子,反演

i=1limif(i)=i=1limii|dF(d)μ(di)=i=1limii|dj=1n((cntj,d+1)1)μ(di)=d=1limi|diμ(di)(j=1n(cntj,d+1)1)

然后,然后~~~好像没戏了
但,我们有伟大的欧拉!!

φ(n)=i=1n[gcd(i,n)=1]=i=1nd|gcd(i,j)μ(d)=d|nμ(d)nd

哈哈哈,太棒了!
相同的一部分,代入式子

d=1limi|diμ(di)(j=1n(cntj,d+1)1)=d=1limφ(n)(j=1n(cntj,d+1)1)

于是这题就这样了。
线性筛 φ,预处理 cnt 数组(根据套路,不要枚举因子而是枚举倍数)。

Code

#include<cstdio>
#include<iostream>
using namespace std;
typedef long long LL;
const int N = 1e5;
const LL mod = 1e9 + 7;
int lim , n , m , a[25][N + 5] , cnt[25][N + 5] , phi[N + 5] , prime[N + 5] , vis[N + 5] , tot;
LL ans;
inline void getPhi()
{
phi[1] = 1;
for(register int i = 2; i <= N; i++)
{
if (!vis[i]) phi[prime[++tot] = i] = i - 1;
for(register int j = 1; j <= tot && prime[j] * i <= N; j++)
{
vis[prime[j] * i] = 1;
if (i % prime[j] == 0)
{
phi[prime[j] * i] = phi[i] * prime[j];
break;
}
phi[prime[j] * i] = phi[i] * (prime[j] - 1);
}
}
}
inline void getCnt()
{
for(register int i = 1; i <= n; i++)
for(register int j = 1; j <= lim; j++)
for(register int k = 2; k * j <= lim; k++)
cnt[i][j] += cnt[i][j * k];
}
int main()
{
freopen("b.in" , "r" , stdin);
freopen("b.out" , "w" , stdout);
scanf("%d%d" , &n , &m);
for(register int i = 1; i <= n; i++)
for(register int j = 1; j <= m; j++)
scanf("%d" , &a[i][j]) , cnt[i][a[i][j]]++ , lim = max(lim , a[i][j]);
getPhi() , getCnt();
for(register int d = 1; d <= lim; d++)
{
LL res = 1;
for(register int j = 1; j <= n; j++) res = res * (LL)(cnt[j][d] + 1) % mod;
ans = (ans + (LL)((LL)phi[d] * (res - 1)) % mod) % mod;
}
printf("%lld" , ans);
}
posted @   leiyuanze  阅读(183)  评论(1编辑  收藏  举报
编辑推荐:
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
点击右上角即可分享
微信分享提示