GCD SUM
GCD Sum
题意
给定一个\(n\times m\)的矩阵,每次挑选一个\(k \in [2,n]\),然后挑选\(k\)行,再在这\(k\)行里分别挑\(1\)个元素,计算出他们的\(\gcd\),再计算所有挑选方法所得的\(\gcd\)的和。
题解
如果真按题目描述的那样来模拟着做,将会非常麻烦,于是转化思路,所有的\(\gcd\)只会在\([1,10^5]\)区间内,我们计算每个值被算了多少次即可。那如何计算出每个值被计算了多少次呢?
我们设立一个二维数组\(cnt\),\(cnt[i][j]\)代表了数字\(j\)在第\(i\)行出现的次数。再设立一个数组\(cc\),\(cc[i][j]\)代表数字\(j\)及其它的倍数在第\(i\)行出现的次数。对于数字\(j\),他被计算的次数需要稍微推导一下:
对于一个\(n=4\)的情况,我们假设在第\(1,2,3,4\)行\(j\)及其它的倍数出现的次数为\(a,b,c,d\),那么数字\(j\)被计算的次数就为:
\(k=2:ab+ac+ad+bc+bd+cd\)
\(k=3:abc+abd+bcd\)
\(k=4:abcd\)
利用一下高中学过的知识就可判断出他是\((1+a)(1+b)(1+c)(1+d)\)的展开式再减去\(a+b+c+d+1\)。
但还没完,我们这样算其实是算多了的,\(\gcd=kj,k=2,3,4,...\)的情况也被算进去了,减掉即可。这样一来,数字\(j\)对答案的贡献就是\(j\times \sum cc[i][j]-\sum_{t=2}ans[tj]\)。\(ans\)数组用来记录答案,\(ans_j\)就是数字\(j\)对答案的贡献。
\(cc\)数组的第二维每次循环只用到了一次,所以可以只需要开一维的即可。
时间复杂度:\(O(nM\log{M})\)。
AC代码
#pragma GCC optiminze(2)
#include "bits/stdc++.h"
#define IO ios::sync_with_stdio(NULL)
#define sc(z) scanf("%d", &(z))
#define _ff(i, a, b) for (ll i = a; i <= b; ++i)
#define _rr(i, a, b) for (ll i = b; i >= a; --i)
#define _f(i, a, b) for (ll i = a; i < b; ++i)
#define _r(i, a, b) for (ll i = b - 1; i >= a; --i)
#define mkp make_pair
#define endl "\n"
#define lowbit(x) x&(-x)
#define pb push_back
using namespace std;
typedef double db;
typedef long long ll;
typedef long double ld;
const int N = 20 + 5;
const int M = 1e5 + 5;
const ll mod = 1e9 + 7;
const double eps = 1e-9;
const double pi = acos(-1.0);
ll a[N][M], cnt[N][M], dp[M], cc[N];
void solve() {
int n, m; cin >> n >> m;
_ff(i,1,n)_ff(j,1,m)cin>>a[i][j];
ll ans = 0;
_ff(i,1,n)_ff(j,1,m)cnt[i][a[i][j]]++;
_r(i,1,M){
_ff(l,1,n){
cc[l]=0;
for (int j=i;j<M;j+=i)cc[l]+=cnt[l][j];//埃氏筛筛出第l行所有i的倍数出现的次数
}
dp[i]=1;
_ff(j,1,n)dp[i]=dp[i]*(1+cc[j]) % mod;
_ff(j,1,n)dp[i]=(dp[i]-cc[j]+mod)%mod;
dp[i]=(dp[i]-1+mod)%mod;
for(int j=i+i;j<M;j+=i)dp[i]=(dp[i]-dp[j]+mod)%mod;//减去所有gcd为i的倍数的答案
ans=(ans+i*dp[i]%mod)%mod;
}
cout<<ans<<endl;
}
int main() {
IO;
solve();
return 0;
}