23届专题训练:简单数学

B-质因数分解

#include<bits/stdc++.h>

using namespace std;
typedef long long ll;

int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    int n; cin >> n;
    int mx = 0;
    for (int i = 2; i <= n / i; i++){
        if (n % i == 0){
            while (n % i == 0){
                n /= i;
            }
            mx = max(mx, i);
        }
    }
    if (n > 1) mx = max(n, mx); //n当中最多只包含1个大于sqrt(n)的质因数
    cout << mx;
    return 0;
}

在一般情况下找质因数可以遍历所有小于等于n的数,如果能被n整除并且这个数是一个质数,那么这个数就是n的质因子。但是这样找的时间复杂度较高,遍历一遍为O_n,判断质数为\sqrt{n},总时间复杂度为O_{n\sqrt{n}}

首先我们来了解一个定理:任何一个正整数可以被分解成若干个质数的乘积。

借助这个定理我们可以把分解质因数的时间复杂度降为logn(最好情况),最坏情况下为\sqrt{n}

因为1不是一个质数,所以我们从2开始遍历。当i能够被n整除的时候,in的一个质因子。我们让n除以i,直到n不能被i整除。考虑到上面的定理,这个操作可以保证i一定不是合数。

但是我们只遍历到了\sqrt{n},但是n当中可能存在大于\sqrt{n}的质因子。比如21,它的质因子为3和7。

但是我们遍历完之后,是无法访问到7的。但是再考虑一下上面的定理,我们可以发现n最多只会存在一个大于\sqrt{n}的质因子,因为\sqrt{n}*\sqrt{n}=n

A - 线性筛素数

这个题目给的数据范围特别大,用一般的埃式筛是过不去的。这里给大家介绍一种最快的求素数的方法——欧拉筛,时间复杂度是O_n

先上代码:

#include<bits/stdc++.h>

using namespace std;

const int N = 1e8 + 7;

int a[N];//a用来存素数
int st[N];//st用来标记是否访问过,
          //也可以用来记录某个是是否为素数,没有被标记的为素数

void solve(){
    st[0] = 1, st[1] = 1; //0和1都不是素数
    for (int i = 2; i <= N; i++){
        if (!st[i]){ //如果遇到素数,存入a数组
            a[++a[0]] = i;
        }
        //将i的所有倍数都标记为合数(非素数)
        for (int j = 1; j <= a[0] && i * a[j] <= N; j++){
            st[i * a[j]] = 1;
            if (i % a[j] == 0) break; //保证不会重复标记
        }
    }
}

int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    solve();
    int n, q;
    cin >> n >> q;
    while (q--){
        int k; cin >> k;
        cout << a[k] << '\n';
    }
    return 0;
}

这里我们还会用到质因数分解时提到的那个定理:任何一个正整数可以被分解成若干个素数的乘积。

因为时间有限,我只给大家讲一下欧拉筛的大概思路,更为具体的细节与证明大家可以自己去学习。首先,素数的定义为:在大于1的自然数中,除了1和该数自身外, 无法被其他自然数整除 的数。所以当我们把每个数的倍数都标记了之后,剩下来的就都是素数了。

又因为前面提到的定理,可以发现我们只需要把所有的素数的倍数都标记一遍,就可以把所有的合数都标记出来了(因为如何一个数都能由若干个素数相乘得到)。至于为什么要在i%primes[j]的时候break,大家可以自己后面再去思考一下。

C-Non-coprime Split

#include<bits/stdc++.h>

using namespace std;
typedef pair<int, int> PII;

void solve(){
    int l, r;
    cin >> l >> r;
    for (int i = l; i <= r; i++){ //枚举a+b的值
        for (int j = 2; j <= i / j; j++){ //枚举a的值
            if (i % j == 0){ //如果找到了a+b的因子
                //假设i为j的k倍,i-j就是j的(k-1)倍,所以gcd((i-j),j)!=1
                cout << j << ' ' << i - j << '\n';
                return;
            }
        }
    }
    cout << "-1\n";
}

int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    int T; cin >> T;
    while (T--){
        solve();
    }
    return 0;
}

这里需要知道,当i%j==0的时候,令k=i/j,那么(i-j)/j=k-1,所以(i-j)%j==0。

题目只限制了a+b的值,并且只需要我们输出如何一组答案即可。我们就可以直接在[l,r]的范围里面去枚举a+b的值,再去枚举这个值的因数,如果存在不为1的因数,那么我们就得到的ab的值和a+b的值,也就得到了答案。如果在[l,r]的范围里面找不到答案,说明不存在答案,输出-1

D-Plus Minus Permutation

#include<bits/stdc++.h>

using namespace std;
typedef long long ll;

ll gcd(ll a, ll b){
    return b ? gcd(b, a % b) : a;
}

ll lcm(ll a, ll b){
    return a * b / gcd(a, b);
}

int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    int T; cin >> T;
    while (T--){
        ll n, x, y;
        cin >> n >> x >> y;
        ll t = lcm(x, y);
        //求出A和B中出现的共同元素个数
        t = n / t;
        ll sum = 0;
        //分别求出A和B中的元素个数
        x = n / x, y = n / y;
        //求首项为n,公差为-1,长度为x-t的数列和
        sum = (x - t) * n - (x - t - 1) * (x - t) / 2;
        //求首项为1,公差为1,长度为y-t的数列和
        sum -= (y - t ) + (y - t - 1) * (y - t) / 2;
        cout << sum << '\n';
    }
    return 0;
}

题目中的排列是由我们自己给出的。我们下标能被x整除的数都算入集合A,下标能被y整除的数都算入集合B。因为我们要求标能被x整除的数的和减去下标能被y整除的数的和,也就是集合A中的元素和减去集合B中的元素和。可以发现,集合A和集合B中存在交集,交集的个数为n/lcm(x,y)。因为我们可以随意的制造想要的数组,所以我们尽可能的把大的数放入集合A,小的数放入集合B,尽可能大就从n开始递减的放,尽可能小就从1开始递增放。在利用等差数列求和就能O(1)求出答案。

H-Serval and Mocha's Array

#include <bits/stdc++.h>

using namespace std;

int a[105];

int gcd(int a, int b){
    return b > 0 ? gcd(b, a % b) : a;
}

void solve(){
    int n, flag = 0;
    cin >> n;
    for (int i = 1; i <= n; i++){
        cin >> a[i];
    }
    //直接两重循环遍历所有ai和aj
    for (int i = 2; i <= n; i++){
        for (int j = 1; j < i; j++){
            int x = gcd(a[i], a[j]);
            if (x <= 2){ //如果存在两个数的GCD<=2
                cout << "Yes\n";
                return;
            }
        }
    }
    cout << "No\n";
}

int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    int t; cin >> t;
    while (t--){
        solve();
    }
    return 0;
}

题意可以简化为如果存在两个数的GCD小于等于2,即说明数组是美丽的。

直接暴力对数组中的任意两个数求GCD,如果存在GCD\leqslant2,输出"Yes",否则输出"No"。

K-Doremy's Perfect Math Class

#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 7;
int a[N];
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    int t; cin >> t;
    while (t--){
        int n;
        cin >> n;
        int GCD = 0;
        for (int i = 1; i <= n; i++){
            cin >> a[i];
            GCD = __gcd(GCD, a[i]);
        }
        cout << a[n] / GCD << '\n';
    }
    return 0;
}

具体分析题目中的操作,可以发现:选择xy不停的操作,最后会得到gcd(x,y)

对数组中的所有数求一次GCD,最后能加到数组S中的数一定是GCD的倍数,我们又知道最大值,所以用最大值a_n/GCD就是最终S包含的元素个数。

J-Number Factorization

#include<bits/stdc++.h>

using namespace std;

void solve (){
    map<int, int> mp;
    int n; cin >> n;
    //分解质因数
    for (int i = 2; i <= n / i; i++){
        if (n % i == 0){
            int res = 0;
            while (n % i == 0){
                n /= i, res++;
            }
            mp[i] = res;
        }
    }
    if (n > 1) mp[n] = 1;
    int ans = 0;
    //当数组中的存在pi大于1时,把所有的ai乘在一起,所有的pi-1
    while (1){
        int f = 0, res = 1;
        for (auto [x, y]: mp){
            if (y){
                if (y > 1) f = 1;
                res *= x;
                mp[x]--;
            }
            
        }
        ans += res;
        if (!f) break;
    }
    cout << ans << '\n';
}

int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    int T; cin >> T;
    while (T--){
        solve();
    }
    return 0;
}

这里还会用到A题和B题中用到的定理:任何一个正整数可以被分解成若干个质数的乘积。

结合分解质因数,我们可以求出所有的a_ip_i。因为题目让我们求\sum{a_i*p_i}的最大值,如果我们直接分解质因数得到的可能不是最大值。当数组中只存在a_1=5,p_1=3时,我们可以把这个数组分解为a_1=25,p_1=1,a_2=5,p_2=1。在没有分解前,我们的\sum{a_i*p_i}为15,分解后\sum{a_i*p_i}为30。可以发现当p_i大于1的时候,我们拿出一个a_i和其他p_j大于1的a_j乘在一起可以使结果变大。

所以当存在p_i大于1时,把对应的所以a_i都拿出来乘在一起,就能得到\sum{a_i*p_i}的最大值。

E-Yet Another Permutation Problem

#include <bits/stdc++.h>

using namespace std;

int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    int T;
    cin >> T;
    while (T--)
    {
        int n;
        cin >> n;
        map<int, int> mp;//确保不会重复输出
        for (int i = 1; i <= n; i++){
            if (mp[i] == 0){
				cout << i << ' ';
				mp[i]++;
				int t = i;
				while (t * 2 <= n){
					t *= 2;
					if (mp[t] == 0) cout << t << ' ';
					mp[t]++;
				}
			}
        }
        cout << '\n';
    }
    return 0;
}

这个题可以看出,我们不停的输出某个数的2倍是可能的一种答案,交上去确实对了。具体我也没有去证明,感兴趣的同学可以自行证明。这题主要是需要注意不重复输出,也不多输出数,可以用map来记录这个数是否出现过了。

F-We Were Both Children

#include<bits/stdc++.h>
 
using namespace std;
typedef long long ll;
typedef pair<int, int> PII;
 
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    int T;
    cin >> T;
    while (T--){
        int n;
        cin >> n;
        vector<int> a(n + 5);
        map<int, int> mp1; //mp1记录跳跃距离相同的青蛙数量
        for (int i = 1; i <= n; i++) {
            cin >> a[i];
            mp1[a[i]]++;
        }
        map<int, int> mp; //mp记录每个位置上会有多少个青蛙经过
        for (auto [x, y] : mp1){
            int t = 0;
            while (t + x <= n){ //每次跳的距离为x
                t += x;
                mp[t] += y;
            }
        }
        int maxn = 0;
        for (auto x : mp){ //遍历求最大值
            maxn = max(maxn, x.second);
        }
        cout << maxn << '\n';
    }
    return 0;
}

因为所有的青蛙跳的距离是固定的,所以a_i相同的青蛙的路线也是相同的,也就意味着会被同时抓住,可以用map存在一起,减少计算次数。因为每只青蛙跳的距离固定是a_i,所以他能到达的位置是a_i,2a_i,3a_i,....,我们就用一个数组把青蛙经过的所有位置都记录下来(但是这是一个稀疏表,我更喜欢用map)。最后遍历每个位置求出最大值即可。

G-Lunatic Never Content

#include<bits/stdc++.h>

using namespace std;

int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    int T;
    cin >> T;
    while (T--){
        int n;
        cin >> n;
        vector<int> a(n + 1);
        for (int i = 1; i <= n; i++){
            cin >> a[i];
        }
        if (n == 1){
            cout << 0 << '\n';
            continue;
        }
        int GCD = 0;
        for (int i = 1 ; i <= n / 2; i++){
            if (a[i] != a[n - i + 1]){
                GCD = __gcd(GCD, abs(a[i] - a[n - i + 1]));
            }
        }
        cout << GCD << '\n';
    }
    return 0;
}

首先需要知道:要使a\%x=b\%xx的值就为\left |a-b \right |。为了使x满足题目要求,我们需要对所有\left |a-b \right |求一次GCD。

I-Lucky Chains

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;

const int N = 1e7;

int a[N], vis[N], f[N];

//欧拉筛筛质数
void solve(){
    vis[1] = 1;
    for (int i = 2; i <= N; i++){
        if (!vis[i]){
            f[i] = i;
            a[++a[0]] = i;
            vis[i] = 1;
        }
        for (int j = 1; j <= a[0] && i * a[j] <= N; j++){
            vis[i * a[j]] = 1;
            f[i * a[j]] = a[j];
            if (i % a[j] == 0) break;
        }
    }
}

//分解质因数
vector<int> factor_small(int x) {
    vector<int> v;
    while (x > 1) {
        int p = f[x];
        while (x % p == 0) {
            x /= p;
        }
        v.push_back(p);
    }
    return v;
}

int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    solve();
    int t;
    cin >> t;
    while (t--)
    {
        ll n, m;
        cin >> n >> m;
        if (__gcd(n, m) != 1){
            cout << "0\n";
            continue;
        } else{
            int x = abs(n - m);
            if (x == 1){
                cout << "-1\n";
                continue;
            } else{
                ll ans = 1e15;
                //求出x的质因数
                vector<int> s = factor_small(x);
                for (int i = 0; i < s.size(); i++){
                    //向上取整求出大于等于n的x的质因子的最小倍数
                    int t = (n + s[i] - 1) / s[i];
                    ans = min(ans, t * s[i] - n);
                }
                cout << ans << '\n';
            }
        }
    }
    return 0;
}

这里有一个基于GCD的知识,数组的GCD,等于差分数组的GCD,即gcd(a_1,a_2,a_3,...,a_n)=gcd(a_1,a_2-a_1,a_3-a_2,...,a_n-a_{n-1})

还有一个关于GCD的知识,gcd(x+k,y+k)=gcd(x+k,y-x),这里和C题提到的性质是相同的。所以当gcd(x+k,y-x)!=1时,gcd(x+k,y+k)!=1

因为当gcd(n+k,m-n)!=1时,一定满足n+km-n的倍数。所以我们再去求出\left | m-n \right |的质因子x满足(m-n)*t\geqslant n时,t的值。k就是每次t*x-n的值,答案就是所有k的最小值。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值