本章内容对我来说真的是学的稀里糊涂的,除了前两题吭哧吭哧独立完成,第三题参考了别人的思路外,其余题目均是现学现卖,有点迷啊。所以写这篇博客的目的是先记录下聚聚们对本章内容相关重点的要求,并搜集一些相关资料,慢慢学吧。
D - GCD & LCM Inverse
题意:
给出两个整数 m、n ( < 2^63),求出两个整数 a、b ,使gcd(a, b)=m , lcm(a, b)=n ,并取 a + b 值最小的那一组。
Thinking:
拿到题看到数据范围后想了想没有思路。看了很多博客,对此题的化简分析有了认识,但实现本题所用的很多知识:
如:Miller-Rabin素性测试算法 和 进行素数分解的Pollard_Rho算法 却一头雾水。
在这里贴上一些博客供以后学习使用:
1、 https://blog.csdn.net/ECNU_LZJ/article/details/72675595
2、 https://blog.csdn.net/semiwaker/article/details/60142102
3 、https://blog.csdn.net/maxichu/article/details/45459533
4 、https://www.cnblogs.com/Norlan/p/5350243.html
5 、https://wenku.baidu.com/view/fbbed5a5f524ccbff12184af.html
通过化简使gcd(a1, b1)=1, 则 lcm(a1, b1)=n/m=s , 把s分解成两互质数s1, s2相乘的形式,这里由于n的范围过大,所以需要用到Miller_Rabin和Pollard_Rho算法将s分解。
要是a+b最小,则要使s1和s2越接近(初中的不等式知识可以证明),此处需要用到dfs()枚举s的num个互质的数如何组合成s1,s2并满足其差最小(这种枚举技巧学习了)。
(代码借鉴了很多前辈的,因为太多就不贴出处了,但还是要说声谢谢分享。)
1 #include <cstdio>
2 #include <cstdlib>
3 #include <cmath>
4 #include <algorithm>
5 using namespace std;
6 #define LL long long
7
8 LL qmul(LL a, LL b, LL c){
9 a %= c;
10 b %= c;
11 LL res = 0;
12 while(b){
13 if(b&1){
14 res = (res+a)%c;
15 }
16 a = (a+a)%c;
17 b >>= 1;
18 }
19 return res;
20 }
21 LL qmodx(LL a, LL b, LL c){
22 a %= c;
23 b %= c;
24 if(c <= 1000000000)
25 return a * b % c;
26 return (a * b - (LL)(a / (long double)c*b + 1e-8) *c + c) % c;
27 }
28 LL qmod(LL a, LL b, LL c){
29 LL res = 1;
30 while(b){
31 if(b&1){
32 res = qmul(res, a, c);
33 }
34 b >>= 1;
35 a = qmul(a, a, c)%c;
36 }
37 return res;
38 }
39 //以a为基,n-1=x*2^t a^(n-1)=1(mod n) 验证n是不是合数
40 //一定是合数返回true,不一定返回false
41 /*//////
42 ?????
43 *////////
44 bool check(LL a, LL n, LL x, LL t){
45 LL ret = qmod(a, x, n);
46 LL last = ret;
47 for(int i=1; i<=t; i++){
48 ret = qmul(ret, ret, n);
49 if(ret == 1 && last!=1 && last!=(n-1)) return true;
50 last = ret;
51 }
52 if(ret != 1) return true;
53 return false;
54 }
55 ////////----------
56 // Miller_Rabin()算法素数判定
57 //是素数返回true.(可能是伪素数,但概率极小)
58 //合数返回false;
59 bool Miller_Rabin(LL n, int S){
60 if(n < 2) return false;
61 if(n == 2) return true;
62 if((n&1) == 0) return false;
63 LL x = n-1, t = 0;
64 while((x&1) == 0){
65 x >>= 1;
66 t++;
67 }
68 for(int i=0; i<S; i++){
69 LL a = rand()%(n-1) + 1;
70 if(check(a, n, x, t)){
71 return false;
72 }
73 }
74 return true;
75 }
76 ///////////------------
77 //************************************************
78 //pollard_rho 算法进行质因数分解
79 //************************************************
80 ///////////----------
81 int factor[1005];//质因数分解结果(刚返回时是无序的)
82 int tol;//质因数的个数。数组小标从0开始
83 LL gcd(LL a, LL b){
84 if(a==0) return 1;
85 if(a < 0) return gcd(-a, b);
86 while(b){
87 LL t = a % b;
88 a = b;
89 b = t;
90 }
91 return a;
92 }
93 LL Pollard_rho(LL x, LL c){
94 LL i = 1, k = 2;
95 LL x0 = rand()%x;
96 LL y = x0;
97 while(1){
98 i++;
99 x0 = (qmul(x0, x0, x) + c)%x;
100 LL d = gcd(y-x0, x);
101 if(d != 1 && d != x) return d;
102 if(y == x0) return x;
103 if(i == k){
104 y = x0;
105 k += k;
106 }
107 }
108 }
109 void findfac(LL n){
110 if(Miller_Rabin(n, 20)){
111 factor[tol++] = n;
112 return;
113 }
114 LL p = n;
115 while(p >= n){
116 p = Pollard_rho(p, rand()%(n-1)+1);
117 }
118 findfac(p);
119 findfac(n/p);
120 }
121 ///////---------
122 LL r[100];
123 int num;
124 LL k, sq;
125 void dfs(LL now, int x){
126 if( now > sq ) return;
127 k = max(k, now);
128 for(int i=x; i<=num; i++){
129 dfs(now*r[i], i+1);
130 }
131 }
132
133
134 int main(){
135 LL gc, lc, n;
136 while(scanf("%lld%lld", &gc, &lc) != EOF){
137 if(gc == lc){
138 printf("%lld %lld\n", gc, lc);
139 continue;
140 }
141 tol = 0;
142 n = lc/gc;
143 findfac(n); sort(factor, factor + tol);
144 num = 0;
145 for(int i=0; i<=50; i++) r[i] = 1;
146 r[num] = factor[0];
147 for(int i=1; i<tol; i++){
148 if(factor[i] == factor[i-1]){
149 r[num] *= factor[i];
150 }else{
151 r[++num] = factor[i];
152 }
153 }
154 k = 1; sq = (LL)sqrt(1.0*n);
155 dfs(1, 0);
156 printf("%lld %lld\n", k*gc, lc/k);
157 }
158 return 0;
159 }
F - GCD
题意: 求(1,b)区间和(1,d)区间里面gcd(x, y) = k的数的对数(1<=x<=b , 1<= y <= d)。注意[(x=5, y=7) and (x=7, y=5) are considered to be the same.]
听赵巨说此题有两种解法,可惜我都不会。
1、本题是一道莫比乌斯反演,就通过这道题了解并学习下。
同样贴出学习用到的博客地址:
下面给出两种mobius函数的求法:
1 /*
2 时间复杂度O(nlogn)
3 这种筛法思路有点奇妙
4 */
5 #include <cstdio>
6 const int maxn = 100;
7 int mu[maxn+20];
8 void get_mu1(){
9 for(int i=1;i<maxn;++i){
10 int delta = (i==1 ? 1-mu[i] : -mu[i]);
11 mu[i]=delta;
12 for(int j=2*i;j<N;j+=i)
13 mu[j]+=delta;
14 }
15 }
16 int main(){
17 get_mu1();
18 for(int i=0; i<maxn; i++){
19 printf("i = %d mu[%d] = %d\n",i, i, mu[i]);
20 }
21 return 0;
22 }
1 /*
2 时间复杂度O(n)
3 这种筛法思路有点像 线性素数筛
4 去掉////则为线性素数筛法
5 */
6 #include <cstdio>
7 const int maxn = 100;
8 int mu[maxn+20], p[maxn+20], vis[maxn+20];
9 void get_mu2(int &cnt){
10 mu[1] = 1;
11 cnt = 0;
12 for(int i=2; i<maxn; i++){
13 if(!vis[i]){
14 p[cnt++] = i;
15 mu[i] = -1; ////
16 }
17 for(int j=0; j<cnt && i*p[j]<maxn; j++){
18 vis[i*p[j]] = 1;
19 if(i % p[j]){ ////
20 mu[i*p[j]] = -mu[i];
21 }else{
22 mu[i*p[j]] = 0; ////
23 break;
24 }
25 }
26 }
27 }
28 int main(){
29 int cnt;
30 get_mu2(cnt);
31 for(int i=0; i<maxn; i++){
32 printf("i = %d mu[%d] = %d\n",i, i, mu[i]);
33 }
34 return 0;
35 }
本题最重要的应该是构造出 f 和 F 两函数并找出其关系,然后通过约数莫比乌斯反演或倍数莫比乌斯反演的转换。
比较难思考的是构造……
1 #include <cstdio>
2 #include <algorithm>
3 using namespace std;
4 #define LL long long
5 const int maxn = 1e5+10;
6 int mu[maxn+20], p[maxn+20], vis[maxn+20];
7 void get_mu2(int &cnt){
8 mu[1] = 1;
9 cnt = 0;
10 for(int i=2; i<maxn; i++){
11 if(!vis[i]){
12 p[cnt++] = i;
13 mu[i] = -1;
14 }
15 for(int j=0; j<cnt && i*p[j]<maxn; j++){
16 vis[i*p[j]] = 1;
17 if(i % p[j]){
18 mu[i*p[j]] = -mu[i];
19 }else{
20 mu[i*p[j]] = 0;
21 break;
22 }
23 }
24 }
25 }
26 int main(){
27 int cnt;
28 get_mu2(cnt);
29 int T, a, b, c, d, k;
30 scanf("%d", &T);
31 for(int t=1; t<=T; t++){
32 scanf("%d%d%d%d%d", &a, &b, &c, &d, &k);
33 printf("Case %d: ", t);
34 if(k == 0){
35 printf("0\n");
36 continue;
37 }
38 b /= k; d /= k;
39 LL ans1 = 0, ans2 = 0;
40 int mi = min(b,d);
41 for(int i=1; i<=mi; i++){
42 ans1 += (LL)mu[i]*(b/i)*(d/i);
43 }
44 for(int i=1; i<=mi; i++){
45 ans2 += (LL)mu[i]*(mi/i)*(mi/i);
46 }
47 printf("%lld\n", ans1-ans2/2);
48 }
49 return 0;
50 }
2、可以用欧拉函数+容斥原理,将问题求(1,n)和(1,m)内素数对的问题转化为 (1,n)内欧拉函数和(的问题)与 (1,n)和(n+1,m)内素数对(的问题)。
关于欧拉函数放上我学习过的文章:
第二个问题容斥原理有3种方法:队列数组 dfs 位运算
1 #include <cstdio>
2 #include <cstring>
3 #include <algorithm>
4 using namespace std;
5 #define LL long long
6 const int maxn = 10000;
7 int prime[maxn+10];
8 //线性素数筛法筛出素数表
9 void getprime(){ //prime[0]记录素数表的长度
10 memset(prime, 0, sizeof(prime));
11 for(int i=2; i<=maxn; i++){
12 if(!prime[i]){
13 prime[++prime[0]] = i;
14 }
15 for(int j=1; j<=prime[0] && prime[j]<=maxn/i; j++){
16 //除法防溢出
17 prime[i*prime[j]] = 1;
18 if(i%prime[j] == 0) break;
19 }
20 }
21 }
22 // 算术展开
23 LL factor[2][100]; //[0][i]:第i个素数值 [1][i]:第i个素数的个数
24 int fatcnt;
25 void getfactors(LL x){
26 fatcnt = 0;
27 LL tmp = x;
28 for(int i=1; prime[i] <= tmp/prime[i]; i++){
29 factor[1][fatcnt] = 0;
30 if(tmp % prime[i] == 0){
31 factor[0][fatcnt] = prime[i];
32 while(tmp % prime[i] == 0){
33 factor[1][fatcnt]++;
34 tmp /= prime[i];
35 }
36 fatcnt++;
37 }
38 }
39 if(tmp != 1){
40 factor[0][fatcnt] = tmp;
41 factor[1][fatcnt++] = 1;
42 }
43 }
44 //欧拉函数打表
45 int euler[100010];
46 void geteuler(){
47 memset(euler, 0, sizeof(euler));
48 euler[1] = 1;
49 for(int i=2; i<=100000; i++){
50 if(!euler[i]){
51 for(int j=i; j <= 100000; j += i){
52 if(!euler[j]){
53 euler[j] = j;
54 }
55 euler[j] = euler[j]/i*(i-1);
56 }
57 }
58 }
59 }
60 //用位运算实现(1,n)内和m互质的个数 ////n<m
61 ///这里代码比较好理解,可以对比赵巨讲的那个2R/7的例子,或者容斥的定义来写
62 int calc(int n, int m){
63 getfactors(m);
64 int ans = 0;
65 ///用二进制枚举m的几个素因子被用到
66 for(int i=1; i < (1<<fatcnt); i++){
67 int cnt = 0;
68 int tmp = 1;
69 for(int j=0; j<fatcnt; j++){
70 //目前第几个因子被使用
71 if(i & (1<<j)){
72 cnt++;
73 tmp *= factor[0][j];
74 }
75 }
76 if(cnt & 1){
77 ans += n/tmp;
78 }else{
79 ans -= n/tmp;
80 }//容斥原理
81 }
82 return (n - ans);
83 }
84 int main(){
85 getprime();
86 geteuler();
87 int a, b, c, d, k, T;
88 scanf("%d", &T);
89 for(int t=1; t<=T; t++){
90 scanf("%d%d%d%d%d", &a, &b, &c, &d, &k);
91 printf("Case %d: ", t);
92 if(k==0 || k>b || k>d){
93 printf("0\n");
94 continue;
95 }
96 if(b > d) swap(b,d);
97 b /= k; d /= k;
98 LL ans = 0;
99 for(int i=1; i<=b; i++){
100 ans += (LL)euler[i];
101 }
102 for(int i=b+1; i<=d; i++){
103 ans += (LL)calc(b, i);
104 }
105 printf("%lld\n", ans);
106 }
107 return 0;
108 }
G - Semi-prime H-numbers
题意:
定义一个数,称之为H-numbers,性质:除4余1;
数分三种:1、unit即1; 2、H-primes:满足H-numbers性质的素数; 3、剩下的是H-composite;
H-semi-prime:两个H-prime相乘;
求h内有多少个H-semi-prime?
Thinking:
就按定义去筛出H-semi-prime数。
1 #include <cstdio>
2 #include <cstring>
3 #define LL long long
4 const int maxn = 1000001;
5 int hprime[maxn+10];
6 int vis[maxn+10];
7 int hcnt;
8 void getprime(){
9 for(int i=5; i<=maxn; i+=4){
10 for(int j=5; (i*j<=maxn)&&(j<=maxn); j+=4){
11 if(!vis[i] && !vis[j]){
12 vis[i*j] = 1;
13 }else{
14 vis[i*j] = -1;
15 //!(-1)==0
16 }
17 }
18 }
19 hcnt = 0;
20 for(int i=4; i<=maxn; i++){
21 if(vis[i] == 1){
22 hcnt++;
23 }
24 hprime[i] = hcnt;
25 }
26 }
27 int main(){
28 getprime();
29 int h;
30 while(scanf("%d", &h) && h){
31 printf("%d %d\n", h, hprime[h]);
32 }
33 return 0;
34 }