目录
- luoguP2568 GCD
- hdu1695 GCD
- luogu3455[POI2007]ZAP-Queries
- luogu2522 [HAOI2011]Problem b
- P4318 完全平方数
- luoguP1403[AHOI2005]约数研究
- luoguP3935 Calculating
- P2158 [SDOI2008]仪仗队
- P1390 公约数的和
- P1447 [NOI2010]能量采集
- luoguP2568 GCD(另一做法)
- [SDOi2012]Longge的问题
- UVA11417 GCD
- GCD - Extreme (II)
- UVa11424 GCD - Extreme (I)
最近在搞莫比乌斯反演,性质还是很不错的,所以把它们挂在了一篇博客上.
很详细的题解,适合刚学莫比乌斯反演找题做的初学者.
upd:2018.9.23 添加了已经挂在博客园的四道题.公式恐惧者要克服恐惧
食用须知
请先学会以下内容,
线性筛(大米饼)
莫比乌斯反演(pengym)
\[写在前面的话\]
刚学会莫比乌斯反演,就去欢乐的切题啦,发现实在是肝不动,利用两天时间才肝了一下几道题.
-----------by Gzy
luoguP2568 GCD
链接:
luoguP2568 && bzoj2818: GCD
题目描述
给定整数N,求1<=x,y<=N且Gcd(x,y)为素数的数对(x,y)有多少对.
输入输出格式
输入格式:
一个整数n
输出格式:
答案
输入输出样例
输入样例#1:
4
输出样例#1: 复制
4题解
考虑每个素数对答案的贡献.
\[gcd(x,y) = p\]
\[gcd(x/p,y/p) = 1\]
就转化成了两个互质的数对对答案贡献1.线性筛phi,求一个前缀和即可.
因为\[(x,y) = (y,x) (x ≠ y)\]所以ans要乘2,由于还有gcd(1,1)这个特殊情况,再-1
// luogu-judger-enable-o2
#include
#include
const int X = 10000001;
int n,num;
long long prime[X],phi[X];
bool is_prime[X];
void init() {
phi[1] = 1;
for(int i = 2;i <= n;++ i){
if(!is_prime[i]){
prime[num ++] = i;
phi[i] = i - 1;
}
for(int j = 0;j < num && i * prime[j] <= n;++ j){
is_prime[i * prime[j]] = true;
if(i % prime[j] == 0){
phi[i * prime[j]] = phi[i] * prime[j];
break;
}
else{
phi[i * prime[j]] = phi[i] * (prime[j] - 1);
}
}
}
}
int main(){
scanf("%d",&n);
init();
for(int i = 2;i <= n;++ i)phi[i] += phi[i - 1];
long long ans = 0;
for(int i = 0;i < num;++ i)ans += phi[n / prime[i]] * 2 - 1;
printf("%lld",ans);
return 0;
}
hdu1695 GCD
题目链接:HDU 1695
Problem Description
Given 5 integers: a, b, c, d, k, you're to find x in a...b, y in c...d that GCD(x, y) = k. GCD(x, y)means the greatest common divisor of x and y. Since the number of choices may be very large, you're only required to output the total number of different number pairs.Please notice that, (x=5, y=7) and (x=7, y=5) are considered to be the same.Yoiu can assume that a = c = 1 in all test cases.
Input
The input consists of several test cases. The first line of the input is the number of the cases. There are no more than 3,000 cases.
Each case contains five integers: a, b, c, d, k, 0 < a <= b <= 100,000, 0 < c <= d <= 100,000, 0 <= k <= 100,000, as described above.
Output
For each test case, print the number of choices. Use the format in the example.
Sample Input
2
1 3 1 5 1
1 11014 1 14409 9
Sample Output
Case 1: 9
Case 2: 736427
HintFor the first sample input, all the 9 pairs of numbers are (1, 1), (1, 2), (1, 3), (1, 4), (1, 5), (2, 3), (2, 5), (3, 4), (3, 5).
注意
a = 1,c = 1,此题细节很多,我都标了括号.
题意:让你求出\[\sum_{i=1}^{b}\sum_{j=1}^{d}[gcd(x,y)=k]\]
跟T1的思路差不多.\[gcd(x,y) = k;gcd(x/k,y/k) = 1\]
然后我们怎么求呢.现在开始莫比乌斯反演.
设函数\[f(d) 是 gcd(x,y) = d\]的对数的个数.
F(d) = \[\sum_{d|n}f(n)\]的对数.
∵\(d|gcd(x,y)\)
∴\(d|x,d|y\)
∴\(F(x) = (a/x) * (b / x)\)(x中有多少x的倍数,y中有多少d的倍数)
反演得到:
\[f(n)=\sum_{n|d}\mu{[d]}F(d)\]
∵我们求得是gcd(x,y) = 1,根据f函数的定义
∴我们求f(1)
f(1) = \[\sum_{i=1}^{min(a,b)} \mu [d]F(d)\](枚举到min(a,b),因为F(d),d最大为min(a,b))
然后欢乐的枚举就好了.
根据题面,我们发现这样会有重复的.
\(gcd(x,y) = gcd(y,x) = 1\)但这样并不被允许,所以我们利用容斥原理,将它删去.
因为重复的都是min(a,b)里面的,所以只要将min(a,b) 到max(a,b)中的数显然不会有重复.
\[\sum_{i=1}^{min(a,b)}\sum_{j=1}^{min(a,b)} [gcd(i,j)==k]\]的答案减半,然后用刚才求得答案减去就好了
#include
#include
#define ll long long
const ll maxN = 100010;
ll mu[maxN];
bool vis[maxN];
ll prime[maxN];
ll num;
void init() {
vis[1] = 1;
mu[1] = 1;
for(ll i = 2;i <= 100000;++ i) {
if( !vis[i] ) {
prime[++ num] = i;
mu[i] = -1;
}
for(ll j = 1;j <= num && i * prime[j] <= 100000;++ j) {
vis[i * prime[j]] = true;
if(i % prime[j] == 0) {
mu[i * prime[j]] = 0;
break;
}
mu[i * prime[j]] = -mu[i];
}
}
}
inline void swap(ll &a,ll &b) {
a ^= b ^= a ^= b;
return;
}
int main() {
ll T;
ll tot = 0;
init();
scanf("%lld",&T);
while(T --) {
ll qaqqqa,qaqqaq,a,b,k;
scanf("%lld%lld%lld%lld%lld",&qaqqqa,&a,&qaqqaq,&b,&k);
if( !k ) {printf("Case %lld: 0\n",++tot);continue;}
printf("Case %lld: ",++ tot);
a = (a / k);b = (b / k);
if(a > b)
swap(a,b);
ll f = 0 ;
ll f2 = 0;
for(ll i = 1;i <= a;++ i)
f += mu[i] * (a / i) * (b / i);
for(ll i = 1;i <= a;++ i)
f2 += mu[i] * (a / i) * (a / i);
printf("%lld\n",f - f2 / 2);
}
return 0;
}
luogu3455[POI2007]ZAP-Queries
题目链接:ZAP-Queries
题目描述
Byteasar the Cryptographer works on breaking the code of BSA (Byteotian Security Agency). He has alreadyfound out that whilst deciphering a message he will have to answer multiple queries of the form"for givenintegers aa , bb and dd , find the number of integer pairs (x,y)(x,y) satisfying the following conditions:
$1\le x\le a1≤x≤a , 1\le y\le b1≤y≤b , gcd(x,y)=dgcd(x,y)=d $, where \(gcd(x,y)gcd(x,y)\) is the greatest common divisor of xx and yy ".
Byteasar would like to automate his work, so he has asked for your help.
TaskWrite a programme which:
reads from the standard input a list of queries, which the Byteasar has to give answer to, calculates answers to the queries, writes the outcome to the standard output.
FGD正在破解一段密码,他需要回答很多类似的问题:对于给定的整数a,b和d,有多少正整数对x,y,满足x<=a,y<=b,并且gcd(x,y)=d。作为FGD的同学,FGD希望得到你的帮助。
输入输出格式
输入格式:
The first line of the standard input contains one integer nn ( 1\le n\le 50 0001≤n≤50 000 ),denoting the number of queries.
The following nn lines contain three integers each: aa , bb and dd ( 1\le d\le a,b\le 50 0001≤d≤a,b≤50 000 ), separated by single spaces.
Each triplet denotes a single query.
输出格式:
Your programme should write nn lines to the standard output. The ii 'th line should contain a single integer: theanswer to the ii 'th query from the standard input.
输入样例#1
2
4 5 2
6 4 3
输出样例#1
3
2
有没有发现跟上一个题只修改了一部分,差异:不需要计算重复部分.
那就非常简单了,貌似没有细节.但是,我们会发现,即使O(n*m)时间复杂度也不过了.
所以这个题就会用到数论分块(注意使用数论分块的那部分).如果不会,可以参考数论分块
// luogu-judger-enable-o2
#include
#include
#define ll long long
const int maxN = 50000 + 7;
ll mu[maxN];
ll prime[maxN];
bool vis[maxN];
ll sum[maxN];
void init() {
int num = 0;
mu[1] = 1;
vis[1] = true;
for(int i = 2;i <= 50000;++ i) {
if( !vis[i] ) {
prime[++ num] = i;
mu[i] = -1;
}
for(int j = 1;j <= num && prime[j] * i <= 50000;++ j) {
vis[prime[j] * i] = true;
if(i % prime[j] == 0) {
mu[i * prime[j]] = 0;
break;
}
mu[i * prime[j]] = -mu[i];
}
}
for(int i = 1;i <= 50000;++ i)
sum[i] = sum[i - 1] + mu[i];
return ;
}
void swap(int &a,int &b) {
a ^= b ^= a ^= b;
return;
}
int min(int a,int b) {
return a > b ? b : a ;
}
int main() {
int T;
scanf("%d",&T);
init();
while(T --) {
int a,b,k;
scanf("%d%d%d",&a,&b,&k);
a /= k;b /= k;
if(a > b) swap(a,b);
long long ans = 0;
for(int l = 1,r;l <= a;l = r + 1) {
r = min(a / (a / l),b / (b / l));
ans += 1LL * ( sum[r] - sum[l - 1] ) * (a / l) * (b / l);
}
printf("%lld\n",ans);
}
}
luogu2522 [HAOI2011]Problem b
题目链接
P2522 [HAOI2011]Problemb
题目描述
对于给出的n个询问,每次求有多少个数对(x,y),满足a≤x≤b,c≤y≤d,且gcd(x,y) = k,gcd(x,y)函数为x和y的最大公约数.
输入输出格式
输入格式:
第一行一个整数n,接下来n行每行五个整数,分别表示a、b、c、d、k
输出格式:
共n行,每行一个整数表示满足要求的数对(x,y)的个数
输入输出样例
输入样例#1:
2
2 5 1 5 1
1 5 1 5 2
输出样例#1:
14
3说明
100%的数据满足:1≤n≤50000,1≤a≤b≤50000,1≤c≤d≤50000,1≤k≤50000
同上一题差不多,但是多了区间,利用容斥原理加加减减的就好了.容斥下来是这样的.
\[Ans((1,b),(1,d))−Ans((1,b),(1,c−1))−Ans((1,a−1),(1,d))+Ans((1,a−1),(1,c−1))\]
不多说,上代码,没什么细节.
#include
#include
#define ll long long
const int maxN = 50000 + 7;
bool vis[maxN];
ll prime[maxN];
ll mu[maxN];
ll sum[maxN];
ll a,b,c,d,k;
inline ll read() {
ll x = 0,f = 1;char c = getchar();
while(c < '0' || c > '9') {if(c == '-')f = -1;c = getchar();}
while(c >= '0' && c <= '9') {x = x * 10 + c - '0';c = getchar();}
return x * f;
}
void init() {
ll num = 0;
mu[1] = 1;
vis[1] = 1;
for(ll i = 2;i <= 50000;++ i) {
if( !vis[i] ) {
prime[++ num] = i;
mu[i] = -1;
}
for(ll j = 1;j <= num && i * prime[j] <= 50000;++ j) {
vis[i * prime[j]] = true;
if( i % prime[j] == 0) {
mu[i * prime[j]] = 0;
break;
}
mu[i * prime[j]] = -mu[i];
}
}
for(ll i = 1;i <= 50000;++ i)
sum[i] = sum[i - 1] + mu[i];
return ;
}
void swap(ll &a,ll &b) {
a ^= b ^= a ^= b;
return;
}
ll min(ll a,ll b) {
return a > b ? b : a;
}
ll slove(ll l,ll r) {
l /= k;r /= k;
if(l > r) swap(l,r);
ll ans = 0;
for(ll L = 1,R;L <= l;L = R + 1) {
R = min(l / (l / L),r / (r / L));
ans += (sum[R] - sum[L - 1]) * (l / L) * (r / L);
}
return ans;
}
int main() {
ll T;
T = read();
init();
while(T --) {
a = read();b = read();c = read();d = read();k = read();
if( !k ) {
puts("0");
continue;
}
printf("%lld\n",slove(b,d) - slove(a - 1,d) - slove(c - 1,b) + slove(a - 1,c - 1));
}
}
刷着刷着就刷不动了,因为我遇到了一个很奇怪的题目.
P4318 完全平方数
题目描述
小 X 自幼就很喜欢数。但奇怪的是,他十分讨厌完全平方数。他觉得这些数看起来很令人难受。由此,他也讨厌所有是完全平方数的正整数倍的数。然而这丝毫不影响他对其他数的热爱。这天是小X的生日,小 W 想送一个数给他作为生日礼物。当然他不能送一个小X讨厌的数。他列出了所有小X不讨厌的数,然后选取了第 K个数送给了小X。小X很开心地收下了。然而现在小 W 却记不起送给小X的是哪个数了。你能帮他一下吗?
输入输出格式
输入格式:
包含多组测试数据。文件第一行有一个整数 \(T\) ,表示测试数据的组数.第\(2\) 至第 \(T+1\) 行每行有一个整数 \(K_i\) ,描述一组数据,含义如题目中所描述。
输出格式:
含T 行,分别对每组数据作出回答。第 \(i\) 行输出相应的第 \(K_i\) 个不是完全平方数的正整数倍的数。
输入输出样例
输入样例#1:
4
1
13
100
1234567
输出样例#1:
1
19
163
2030745
说明
对于 50%的数据有 \(1 ≤ K_i ≤ 10^5\) , 对于 100%的数据有 \(1 ≤ K_i ≤ 10^9, T ≤ 50\)
题意意思是求第k个无平方因子数是多少。无平方因子数,即对其进行质因数分解之后所有质因数的次数都为1的数.
一看这道题,不就是一个\(\mu\)函数的妙用么,统计一下不为0的\(\mu\)值不就好了,秒了.
然后码了以下代码.
#include
#include
const int maxN = 10000000 + 7;
int prime[maxN / 10];
bool vis[maxN];
int mu[maxN];
void init() {
int num = 0;
mu[1] = 1;
vis[1] = 1;
for(int i = 2;i <= 10000000;++ i) {
if( !vis[i] ) {
prime[++ num] = i;
mu[i] = -1;
}
for(int j = 1;j <= num && i * prime[j] <= 10000000;++ j) {
vis[i * prime[j]] = true;
if(i % prime[j] == 0) {
mu[i * prime[j]] = 0;
break;
}
mu[i * prime[j]] = -mu[i];
}
}
}
int slove(int k) {
int num = 0;
for(int i = 1;i <= 10000000;++ i) {
if(mu[i] == 1 || mu[i] == -1) num++;
if(num == k) return i;
}
}
int main() {
int T;
scanf("%d",&T);
init();
while(T --) {
int k;
scanf("%d",&k);
printf("%d\n",slove(k));
}
return 0;
}
然后很快的我TLE了三个点,一看数据范围,O(n)也过不去,只有类似于O(\(\sqrt n\))的时间复杂度才可行.
\(k=x-\sum_{i=1}^{x}{(1-|\mu(i)|)}\)(x不确定)
因为k不一定在哪一个x,我们会发现可以x越大区间[1,k]的无平方因子数越多,那么就可以欢快的二分了.
但是如何求区间[1,x]的无平方因子数呢.还是考虑容斥.
设当前二分到了x,那么不符合条件的数是.
\[\sum_{i\in{prime}}^{\sqrt{x}}{\frac{x}{i^2}} \]
显然这个式子会重复计算一个数,我们再减去重复的,同理剩下的也是这样.
容易发现前面的系数是\(\mu [i]\)(别问我,我不知道(捂脸))
式子就变为\[k=x-\sum_{i=1}^{\sqrt{x}}{\mu(i)*\frac{x}{i^2}}\]
二分的上界可以自己测出来.
还有一道类似的题目:[vijos1889]天真的因数分解跟这道题差不多,代码就懒得写了.
代码:
// luogu-judger-enable-o2
#include
#include
#include
#define ll long long
const ll maxN = 50000 + 7;
using namespace std;
bool vis[maxN];
ll prime[maxN];
ll mu[maxN];
ll sum[maxN];
void init() {
ll num = 0;
mu[1] = 1;
for(ll i = 2;i <= 50000;++ i) {
if( !vis[i] ) {
prime[++ num] = i;
mu[i] = -1;
}
for(ll j = 1;j <= num && i * prime[j] <= 50000;++ j) {
vis[i * prime[j]] = true;
if(i % prime[j] == 0) {
mu[i * prime[j]] = 0;
break;
}
mu[i * prime[j]] = -mu[i];
}
}
return ;
}
ll work(ll k) {
ll ans = 0;
ll f = sqrt(k);
for(ll l = 1;l <= f;++ l) {
ans += mu[l] * (k / l / l);
}
return ans;
}
bool calc(ll x,ll k) {
return x >= k;
}
int main() {
int T;
init();
scanf("%d",&T);
while(T --) {
ll k;
scanf("%lld",&k);
ll l = 1,r = 2000000000,ans;
while(l <= r) {
ll mid = (l + r) >> 1;
ll x = work(mid);
if(calc(x,k)) {
ans = mid;
r = mid - 1;
}
else l = mid + 1;
}
printf("%lld\n",ans);
}
return 0;
}
luoguP1403[AHOI2005]约数研究
题链:luoguP1403[AHOI2005]约数研究
题目描述
科学家们在Samuel星球上的探险得到了丰富的能源储备,这使得空间站中大型计算机“Samuel II”的长时间运算成为了可能。由于在去年一年的辛苦工作取得了不错的成绩,小联被允许用“Samuel II”进行数学研究.
小联最近在研究和约数有关的问题,他统计每个正数N的约数的个数,并以f(N)来表示。例如12的约数有1、2、3、4、6、12。因此f(12)=6。下表给出了一些f(N)的取值:
f(n)表示n的约数个数,现在给出n,要求求出f(1)到f(n)的总和。
输入输出格式
输入格式:
输入一行,一个整数n
输出格式:
输出一个整数,表示总和
输入输出样例
输入样例#1: 复制
3
输出样例#1: 复制
5说明
【数据范围】
20%N<=5000
100%N<=1000000
直接无语了,竟然被评为了普及,QAQ,发现自己数学简直是渣渣,这么明显的结论推不出.
其实题目让求.\(\sum_{i=1}^{n}f[i]\)
就是因子个数,换个角度,看看每个数被用了多少次,就是\(\sum_{i=1}^{n}\lfloor\frac ni\rfloor\)
可以不用数论分块求.O(n)的可过.
// luogu-judger-enable-o2
#include
#include
#define ll long long
ll slove(ll n) {
ll ans = 0;
for(ll l = 1,r;l <= n;l = r + 1) {
r = n /(n / l);
ans += (r - l + 1) * ( n / l);
}
return ans;
}
int main() {
ll l,r;
scanf("%lld",&r);
printf("%lld",(slove(r)));
return 0;
}
luoguP3935 Calculating
切了上面那道题后, 然后再切这么一道水题.
题目链接:luoguP3935 Calculating
先容斥然后
用到一个定理:因数个数定理
设 \(n=p_1^{k_1}*p_2^{k_2}*...*p_m^{k_m}\)其中 \(p_1,p_2,...,p_m\)均为互不相等的质数
则n的因数个数为$\prod_{i=1}^m{(a_i+1)} $
知道了就和上题差不多.多学点定理吧,骚年.
// luogu-judger-enable-o2
#include
#include
#define ll long long
#define mod 998244353
ll slove(ll n) {
ll ans = 0;
for(ll l = 1,r;l <= n;l = r + 1) {
r = n /(n / l);
ans += ((r - l + 1) % mod * ( n / l) % mod) % mod;
ans %= mod;
}
return ans % mod;
}
int main() {
ll l,r;
scanf("%lld%lld",&l,&r);
printf("%lld",((slove(r) % mod) - (slove(l - 1) % mod) + mod) % mod)% mod;
return 0;
}
P2158 [SDOI2008]仪仗队
题目描述
** 作为体育委员,C君负责这次运动会仪仗队的训练。仪仗队是由学生组成的N * N的方阵,为了保证队伍在行进中整齐划一,C君会跟在仪仗队的左后方,根据其视线所及的学生人数来判断队伍是否整齐(如下图)。 现在,C君希望你告诉他队伍整齐时能看到的学生人数。**
输入输出格式
输入格式:
共一个数N
输出格式:
共一个数,即C君应看到的学生人数。
输入输出样例
输入样例#1: 复制
4
输出样例#1: 复制
9
说明
【数据规模和约定】
对于 100% 的数据,1 ≤ N ≤ 40000
考虑斜率,设这个人的位置为原点,函数y = kx(x横坐标,y纵坐标.k斜率)
斜率相同那么只有一点贡献答案.
变换一下k = y/x
∵\((y*p)/(x*p)的斜率 = k\)
∴只考虑\(gcd(x,y) = 1\)
然后筛\(\phi\)就好了,最后答案\(*2 + 1\),原因去看图.
注意,还有n == 1,这个特殊情况.
#include
#include
const int maxN = 40000 + 7;
bool vis[maxN];
int prime[maxN];
int n;
int phi[maxN];
void init() {
phi[1] = 1;
vis[1] = 1;
int num = 0;
for(int i = 2;i < n;++ i) {
if(!vis[i]) {
prime[++ num] = i;
phi[i] = i - 1;
}
for(int j = 1;j <= num && i * prime[j] < n;++ j) {
vis[i * prime[j]] = true;
if(i % prime[j] == 0) {
phi[i * prime[j]] = phi[i] * prime[j];
break;
}
phi[i * prime[j]] = phi[i] * (prime[j] - 1);
}
}
}
int main() {
scanf("%d",&n);
init();
if(n == 1) {
printf("0");
return 0;
}
long long ans = 0;
for(int i = 1;i < n;++ i) {
ans += 1LL * phi[i];
}
printf("%lld",ans * 2 + 1);
return 0;
}
P1390 公约数的和
题目描述
有一天,TIBBAR和LXL比赛谁先算出1~N这N个数中每任意两个不同的数的最大公约数的和。LXL还在敲一个复杂而冗长的程序,争取能在100s内出解。而TIBBAR则直接想1s秒过而获得完胜,请你帮他完成这个任务。
输入输出格式
输入格式:
共一行,一个正整数N。
输出格式:
共一行,一个数,为1~N这N个数中每任意两个不同的数的最大公约数的和。
输入输出样例
输入样例#1: 复制
10
输出样例#1: 复制
67
说明
对于40%的数据,2≤N≤2000.
对于100%的数据,2≤N≤2000000.
考虑每个数当成gcd,然后按照前几个题的经验乱搞就过了.
细节:矩阵的gcd是排除gcd(x,y)x == y的,所以别忘了减去,因为不要求重复.所以除去一半.
#include
#include
#define ll unsigned long long
const ll maxN= 2000000 + 7;
bool vis[maxN];
ll prime[maxN];
ll mu[maxN];
ll sum[maxN];
void init() {
ll num = 0;
vis[1] = 1;
mu[1] = 1;
for(ll i = 2;i <= 2000000;++ i) {
if( !vis[i] ) {
prime[++ num] = i;
mu[i] = -1;
}
for(ll j = 1;j <= num && prime[j] * i <= 2000000;++ j) {
vis[i * prime[j]] = true;
if(i % prime[j] == 0) {
mu[i * prime[j]] = 0;
break;
}
mu[i * prime[j]] = -mu[i];
}
}
for(ll i = 1;i <= 2000000;++ i)
sum[i] = sum[i - 1] + mu[i];
return;
}
ll slove(ll n,ll k) {
ll ans = 0;
n /= k;
for(ll l = 1,r;l <= n;l = r + 1) {
r = n / (n / l);
ans += ( sum[r] - sum[l - 1] ) * (n / l) * (n / l);
}
return ans;
}
int main() {
ll n;
init();
scanf("%d",&n);
ll ans = 0;
for(ll i = 1;i <= n;++ i) {
ans += i * slove(n,i);
}
printf("%llu",(ans - (n + 1) * n / 2) / 2);
return 0;
}
P1447 [NOI2010]能量采集
题目描述
栋栋有一块长方形的地,他在地上种了一种能量植物,这种植物可以采集太阳光的能量。在这些植物采集能量后,栋栋再使用一个能量汇集机器把这些植物采集到的能量汇集到一起。栋栋的植物种得非常整齐,一共有n列,每列有m棵,植物的横竖间距都一样,因此对于每一棵植物,栋栋可以用一个坐标(x, y)来表示,其中x的范围是1至n,表示是在第x列,y的范围是1至m,表示是在第x列的第y棵。由于能量汇集机器较大,不便移动,栋栋将它放在了一个角上,坐标正好是(0, 0)。能量汇集机器在汇集的过程中有一定的能量损失。如果一棵植物与能量汇集机器连接而成的线段上有k棵植物,则能 量的损失为2k + 1。例如,当能量汇集机器收集坐标为(2, 4)的植物时,由于连接线段上存在一棵植物(1, 2),会产生3的能量损失。注意,如果一棵植物与能量汇集机器连接的线段上没有植物,则能量损失为1。现在要计算总的能量损失。下面给出了一个能量采集的例子,其中n = 5,m = 4,一共有20棵植物,在每棵植物上标明了能量汇集机器收集它的能量时产生的能量损失。在这个例子中,总共产生了36的能量损失.
输入输出格式
输入格式:
仅包含一行,为两个整数n和m。
输出格式:
仅包含一个整数,表示总共产生的能量损失。
输入输出样例
输入样例#1: 复制
5 4
输出样例#1: 复制
36
输入样例#2: 复制
3 4
输出样例#2: 复制
20
说明
对于10%的数据:1 ≤ n, m ≤ 10;
对于50%的数据:1 ≤ n, m ≤ 100;
对于80%的数据:1 ≤ n, m ≤ 1000;
对于90%的数据:1 ≤ n, m ≤ 10,000;
对于100%的数据:1 ≤ n, m ≤ 100,000。
将图转换一下.就是求\[\sum_{i=1}^{n}\sum_{i=1}^{m}2*(gcd(i,j) -1) -1\](根据仪仗队这道题结合图来推这个结论)
我们修改成我们可以做的.
\[\sum_{i=1}^{n}\sum_{i=1}^{m}2*gcd(i,j) - 2 + 1\]
\[\sum_{i=1}^{n}\sum_{i=1}^{m}2*gcd(i,j) - 1\]
然后欢快的提出2.
\[2*\sum_{i=1}^{n}\sum_{i=1}^{m}gcd(i,j)\]
再减去n*m
\[2*\sum_{i=1}^{n}\sum_{i=1}^{m}gcd(i,j) - n*m\]
中间的
\[\sum_{i=1}^{n}\sum_{i=1}^{m}gcd(i,j)\]
用上一题套路来做,还不用容斥,简单.
暴力(WA80)
// luogu-judger-enable-o2
#include
#include
int gcd(int a,int b) {
return !b ? a : gcd(b,a % b);
}
int g[10007];
int main() {
int n,m;
int G = 0;
scanf("%d%d",&n,&m);
for(int i = 1;i <= n;++ i) {
for(int j = 1;j <= m;++ j) {
G += 2 * ( gcd(i,j) - 1) + 1;
}
}
printf("%d",G);
}
正解
#include
#include
#include
#define ll long long
const int maxN = 100000 + 7;
using namespace std;
ll mu[maxN];
ll prime[maxN];
bool vis[maxN];
ll sum[maxN];
void init() {
ll num = 0;
vis[1] = 1;
mu[1] = 1;
for(ll i = 2;i <= 100000;++ i) {
if( !vis[i] ) {
prime[++ num] = i;
mu[i] = -1;
}
for(ll j = 1;j <= num && i * prime[j] <= 100000;++ j) {
vis[i * prime[j]] = true;
if( i % prime[j] == 0) {
mu[i * prime[j]] = 0;
break;
}
mu[i * prime[j]] = -mu[i];
}
}
for(ll i = 1;i <= 100000;++ i)
sum[i] = sum[i - 1] + mu[i];
return;
}
ll slove(ll n,ll m,ll k) {
n /= k;m /= k;
ll lim = min(n,m);
ll ans = 0;
for(ll l = 1,r;l <= lim;l = r + 1) {
r = min(n / (n / l) , m / (m / l));
ans += 1LL * (sum[r] - sum[l - 1]) * (n / l) * (m / l);
}
return ans;
}//求x∈[1,n] y∈[1,m] GCD(x,y) == k的个数.
int main() {
ll n,m;
init();
scanf("%I64d%I64d",&n,&m);
ll q = min(n,m);
ll Ans = 0;
for(ll i = 1;i <= q;++ i) {
Ans += i * slove(n,m,i);
}
printf("%lld",Ans * 2 - m * n);
}
luoguP2568 GCD(另一做法)
回到第一题,你是不是已经有其他的做法了.
我的想法是:
枚举素数,求出\[\sum_{p∈prime}\sum_{i=1}^{n}\sum_{i=1}^{n}[gcd(i,j)==p]\]
或者用YY的GCD那道题的做法,现在没肝完,以后再补.
// luogu-judger-enable-o2
#include
#include
const int maxN = 10000007;
bool vis[maxN];
int prime[10000000];
int num = 0;
int mu[maxN];
int sum[maxN];
int n;
void init() {
mu[1] = 1;
vis[1] = 1;
for(int i = 2;i <= 10000000;++ i) {
if(!vis[i]) {
prime[++ num] = i;
mu[i] = -1;
}
for(int j = 1;prime[j] * i <= 10000000;++ j) {
vis[i * prime[j]] = true;
if(i % prime[j] == 0) {
mu[i * prime[j]] = 0;
break;
}
mu[i * prime[j]] = -mu[i];
}
}
for(int i = 1;i <= 10000000;++ i)
sum[i] = sum[i - 1] + mu[i];
return;
}
long long slove(int n,int k) {
n /= k;
long long ans = 0;
for(int l = 1,r;l <= n;l = r + 1) {
r = n / (n / l);
ans += 1LL * ( sum[r] - sum[l - 1] ) * (n / l) * (n / l);
}
return ans;
}
int main() {
init();
scanf("%d",&n);
long long ans = 0;
for(int j = 1;prime[j] <= n;++ j)
ans += slove(n,prime[j]);
printf("%lld",ans);
return 0;
}
[SDOi2012]Longge的问题
题解戳这→https://www.cnblogs.com/tpgzy/p/9575879.html
UVA11417 GCD
https://www.cnblogs.com/tpgzy/p/9549753.html
还有两道一系列的
难度大概是递增的.
GCD - Extreme (II)
戳这→https://www.cnblogs.com/tpgzy/p/9549974.html
UVa11424 GCD - Extreme (I)
https://www.cnblogs.com/tpgzy/p/9549718.html
如果有问题,可以与本人探讨.MyQQnum:3361879051