A题:思维题
题目大意:
给你一个正整数n,找到4个正整数a,b,c,d 这四个正整数满足以下条件
1 a+b+c+d = n
2 gcd(a,b)= lcm(c,d).
思路
方法1:
- 如果n为奇数,那我们就可以使得 c = d = 1 那他们的的最小公倍数等于1, 同样为了使得a和b的最大公约数与c和d的最小公倍数相等,那gcd(a,b)应该等于1, 因为c = d = 1,所以c + d = 2 ,(c + d)就是偶数,n又是奇数,我们可以知道 奇数 + 偶数 = 奇数, 所以 a + b 就是 奇数,又因为lcm(c,d) == 1所以我们要使得gcd(a,b)为1 那我们可以使得a为偶数,那b只能是奇数.奇数与偶数的最大公约数只能是1,所以我们可以使得a = 2,b = n - 4,c = 1,d = 1,又因为b > 0 所以特判一下 4
- 如果n为偶数,我们可以使得c = d = 2,使得他们的最小公倍数等于2, 同样为了使得a和b的最大公约数与c和d的最小公倍数相等,那gcd(a,b)应该等于2,因为c = d = 2,所以c + d = 4 ,(c + d)就是偶数,n又是偶数,我们可以知道 偶数 + 偶数 = 偶数, 所以 a + b 就是 偶数,又因为lcm(c,d) ==2所以我们要使得gcd(a,b)为2 那我们可以使得a为偶数,那b也只能是偶数,我们又要gcd(a,b) == 2,所以我们可以使得a = 2,b = n - 4 - 2,c =2 ,d = 2,又b > 0,所以特判一下6
#include <iostream>
using namespace std;
int main()
{
int t;
cin >> t;
while(t -- )
{
int n;
cin >> n;
if(n == 4)
{
cout << "1 1 1 1" << '\n';
continue;
}
if(n % 2 == 1)
cout << 2 << ' ' << n - 4 << ' ' << "1" << ' ' << "1" << '\n';
else
{ // 偶数
if(n != 6)
cout << 2 << ' ' << n - 6 << ' ' << "2" << ' ' << "2" << '\n';
else
cout << "3 1 1 1" << '\n';
}
}
return 0;
}
方法2
我们可以使得lcm(c,d) == 1,也就是可以让c = 1,d = 1,同时gcd(a,b) == 1,可以使得a = 1,或者b = 1,那不管另一个是什么,他们的gcd一定是1,gcd(x,1) == 1,所以我们可以令a = 1,当然也可以令b = 1,那另一个就是n - 3,也就是可以(a = 1,b = n -3 ,c = 1,d = 1)|| (a = n - 3,b = 1,c = 1,d = 1),这里我是令b = 1,
#include <iostream>
using namespace std;
void solve()
{
int n; cin >> n;
cout << n - 3 << ' ' << 1 << ' ' << 1 << ' ' << 1 << '\n';
}
int main()
{
int t; cin >> t;
while( t-- ) solve();
return 0;
}
B题:
题目大意
你每次可以执行以下两个操作中的一个
1 复制任何一个已经存在数组
2 你可以调换任何两个已经存在的数组中的任何两个数字的位置
问你能否用最少**的次数使得原来的数组全部变成同一个数字
思路
我们首先要确定一下最后数组中存在的数字是什么,,我们可以假设最后的数字为X
最终结果的数字一定是原数组里面数字个数最多的那个数字,因为每次复制可以选择的元素都是当前数组中每个数字出现的个数的二倍 比如 2 3 1 4 5 2 复制一次后,数字2的个数变成了4个,数字3的个数是1个,数字1的个数是两个,数字4的个数是2个. 我们的目的是**次数最少,也就是要最少的次数来使得最后存在的数字的数量 >= n
其次 每次复制之后,就执行操作2调换数字的位置使得当前数组中选择的数字 x 的个数变成原来的两倍**** 这样是最优的
重复以上过程,直到X的个数 >= n
因为最终的执行操作2的次数一定是 t1 = n - (x的个数),所以我们只用计算x需要几次 >= n 即可,
#include <iostream>
#include <cmath>
#include <vector>
#include <map>
using namespace std;
map<int,int> mp;
void solve()
{
int n,mx = 0;cin >> n;
for(int i = 1;i <= n;i ++ )
{
int x;
cin >> x;
mp[x]++;
mx = max(mx,mp[x]);
}
mp.clear();
int ans;
ans = n - mx;
while(mx < n) ++ans,mx = mx * 2;
cout << ans << '\n';
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
int t;
cin >> t;
while( t-- )
{
solve();
}
return 0;
}
C题:思维 + 优先队列(这个是大佬的代码)
题目大意
你每次可以执行以下两个操作(这个是有顺序的,先执行1,再执行2)
1 如果该节点的某个子节点被感染了,那该节点就可以感染自己的其他子节点,但是最多感染一个
2 可以选择任何一个未被感染过的节点,感染它
使得全部节点感染所需要的最小次数是多少
思路
从题意我们可以知道,我们要尽可能的使得操作1生效,也就是尽可能使得操作1来感染不同的节点,所以我们先感染子节点多的,然后再感染子节点少的 ,这样是最优的,所以我们每一轮感染的顺序就是从子节点多的到子节点少的,重复这个操作,直到被感染完全,这里也有一个注意点 是根节点也要参与比较,所以额外加一个下标来标记根节点 ,sum[i]是以i为父节点的节点个数,因为i是从1开始的,所以我们可以用sum[0] = 1;让一个虚拟的父节点来存根节点,这个感染顺序的话可以用大根堆,也就是优先队列来存储
#include <bits/stdc++.h>
#define ios ios::sync_with_stdio(false)
#define cint cin.tie(0)
using namespace std;
const int N = 2e5 + 100;
int sum[N];
void solve()
{
int n,x;cin >> n;sum[0] = 1;
for(int i = 2;i <= n;i ++ )
{
cin >> x;sum[x]++; // 以x为父节点的节点个数
}
vector<int> num;
for(int i = 0;i <= n;i ++ )if(sum[i]) num.push_back(sum[i]);// 如果i没有子节点就跳过
sort(num.begin(),num.end());
priority_queue<int> q;
int res = num.size(); //为了能够让每一个父节点能够进行操作1 ,所需要的次数
t2 = 0;
for(int i = 0;i < num.size();i ++ ) q.push(num[i] - (i + 1)); // 个数少的就减去的少,
//个数多的就减去的多,最少减去一个
// 是为了能够使得每一个父节点能够进行操作1,
//因为是首先感染子节点最多的父节点,所以该父节点减去的最多。
while(q.size())
{
int t = q.top();q.pop();
if(t <= 0)continue;
if(t <= t2)break;// 这个t2就相当于每一个父节点的子节点已经被感染的个数
// 如果t <= t2 就代表所有的节点都被感染了 ,因为q是大根堆,所以每次出来的一定是所有父节点
// 中子节点最多的
q.push(t - 1);// 这个是操作2,每一次操作2以后,都肯定有一个未被感染的节点被感染.
t2++;
}
cout << res + t2 << '\n';
for(int i = 0;i <= n;i ++ )
sum[i] = 0;
}
int main()
{
ios;cint;
int t;cin >> t;
while(t -- ) solve();
return 0;
}