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;
}
在一般情况下找质因数可以遍历所有小于等于的数,如果能被
整除并且这个数是一个质数,那么这个数就是
的质因子。但是这样找的时间复杂度较高,遍历一遍为
,判断质数为
,总时间复杂度为
。
首先我们来了解一个定理:任何一个正整数可以被分解成若干个质数的乘积。
借助这个定理我们可以把分解质因数的时间复杂度降为(最好情况),最坏情况下为
。
因为1不是一个质数,所以我们从2开始遍历。当能够被
整除的时候,
为
的一个质因子。我们让
除以
,直到
不能被
整除。考虑到上面的定理,这个操作可以保证
一定不是合数。
但是我们只遍历到了,但是
当中可能存在大于
的质因子。比如21,它的质因子为3和7。
但是我们遍历完之后,是无法访问到7的。但是再考虑一下上面的定理,我们可以发现最多只会存在一个大于
的质因子,因为
。
A - 线性筛素数
这个题目给的数据范围特别大,用一般的埃式筛是过不去的。这里给大家介绍一种最快的求素数的方法——欧拉筛,时间复杂度是。
先上代码:
#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和该数自身外, 无法被其他自然数整除 的数。所以当我们把每个数的倍数都标记了之后,剩下来的就都是素数了。
又因为前面提到的定理,可以发现我们只需要把所有的素数的倍数都标记一遍,就可以把所有的合数都标记出来了(因为如何一个数都能由若干个素数相乘得到)。至于为什么要在%
的时候
,大家可以自己后面再去思考一下。
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;
}
这里需要知道,当%
==0的时候,令
,那么
,所以(
)%
==0。
题目只限制了的值,并且只需要我们输出如何一组答案即可。我们就可以直接在
的范围里面去枚举
的值,再去枚举这个值的因数,如果存在不为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;
}
题目中的排列是由我们自己给出的。我们下标能被整除的数都算入集合
,下标能被
整除的数都算入集合
。因为我们要求标能被
整除的数的和减去下标能被
整除的数的和,也就是集合
中的元素和减去集合
中的元素和。可以发现,集合
和集合
中存在交集,交集的个数为
。因为我们可以随意的制造想要的数组,所以我们尽可能的把大的数放入集合
,小的数放入集合
,尽可能大就从
开始递减的放,尽可能小就从
开始递增放。在利用等差数列求和就能
求出答案。
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,如果存在GCD2,输出"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;
}
具体分析题目中的操作,可以发现:选择和
不停的操作,最后会得到
。
对数组中的所有数求一次GCD,最后能加到数组中的数一定是GCD的倍数,我们又知道最大值,所以用最大值
就是最终
包含的元素个数。
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题中用到的定理:任何一个正整数可以被分解成若干个质数的乘积。
结合分解质因数,我们可以求出所有的和
。因为题目让我们求
的最大值,如果我们直接分解质因数得到的可能不是最大值。当数组中只存在
=5,
=3时,我们可以把这个数组分解为
=25,
=1,
=5,
=1。在没有分解前,我们的
为15,分解后
为30。可以发现当
大于1的时候,我们拿出一个
和其他
大于1的
乘在一起可以使结果变大。
所以当存在大于1时,把对应的所以
都拿出来乘在一起,就能得到
的最大值。
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;
}
因为所有的青蛙跳的距离是固定的,所以相同的青蛙的路线也是相同的,也就意味着会被同时抓住,可以用map存在一起,减少计算次数。因为每只青蛙跳的距离固定是
,所以他能到达的位置是
,我们就用一个数组把青蛙经过的所有位置都记录下来(但是这是一个稀疏表,我更喜欢用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;
}
首先需要知道:要使,
的值就为
。为了使
满足题目要求,我们需要对所有
求一次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的知识,,这里和C题提到的性质是相同的。所以当
时,
。
因为当时,一定满足
是
的倍数。所以我们再去求出
的质因子
满足
时,
的值。
就是每次
的值,答案就是所有
的最小值。