A题-在子集上加一
题目
Polycarp得到了一个整数数组a[1…n]作为礼物。现在,他希望执行一定数量的操作(可能为零),使数组的所有元素变得相同(即,成为a1=a2)=⋯=)。
在一次操作中,他可以获取数组中的一些索引,并将这些索引处的数组元素增加1。
例如,设a=[4,2,1,6,2]。他可以执行以下操作:选择索引1、2和4,并将这些索引中的数组元素增加1。因此,在一次操作中,他可以获得数组a=[5,3,1,7,2]的新状态。
使数组的所有元素彼此相等(即变为a1=a2)所需的最小操作数是多少=⋯=一个?
题目概括: 这道题的意思就是所每次操作可以选一些数组中的元素加一,问最少需要多少次操作可以使数组中所有的元素相同。
思路
本人比赛时做题的思路,也是最笨的思路,首先让数组中的所有的元素先按从小到大的顺序排序,记录数组中的最大值,让小于最大值的元素都加一,但是这样操作太慢了,时间复杂度太高,所以我就选择从最大的元素到最小的元素开始,记算每一个元素与最大值的差值,在记录每次增加了几个1,这样一直遍历到最小的元素就得到了结果。
看了官方题解的思路:只需要求数组中最大元素与最小元素的差值就行了,因为每次操作都是在数组的子集上加1,所以让所有小于最大的元素都加1,所以只需要求最小的元素与最大元素的差值即可。
个人感受:我的思路还是比官方题解的思路差了很多,还需要多思考,一定会有更好的解决办法。
代码
本人提交时的代码
#include <bits/stdc++.h>
using namespace std;
int dx[4] = {1, -1, 0, 0};
int dy[4] = {0, 0, 1, -1};
const int N = 1e6 + 10;
typedef long long ll;
typedef pair<int, int> PII;
int t;
int a[100];
int n;
int main()
{
cin >> t;
while (t--)
{
ll ans = 0;
cin >> n;
for (int i = 1; i <= n; i++)
cin >> a[i];
sort(a + 1, a + n + 1);
int k = a[n];
for (int i = n - 1; i >= 1; i--)
{
ans += k - a[i] - ans;
}
cout << ans << endl;
}
return 0;
}
官方题解代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define forn(i, n) for (int i = 0; i < int(n); i++)
void solve()
{
int n;
cin >> n;
int a[n];
for (int i = 0; i < n; ++i)
{
cin >> a[i];
}
int MIN = INT_MAX;
int MAX = INT_MIN;
for (int i = 0; i < n; ++i)
{
MIN = min(MIN, a[i]);
MAX = max(MAX, a[i]);
}
cout << MAX - MIN << '\n';
}
int main()
{
int tests;
cin >> tests;
forn(tt, tests)
{
solve();
}
}
B题-制作AP
题目
Polycarp有3个正整数a、b和c。他可以精确执行以下操作一次。
选择一个正整数m,将整数a、b或c中的一个正好乘以m。
Polycarp是否可以使三个数字a、b、c的序列(按此顺序)在执行操作后形成算术级数?请注意,不能更改a、b和c的顺序。
形式上,如果存在一个数字d(称为“公共差”),那么序列x1,x2,…,xn被称为算术级数(AP),使得从1到n的所有i的xi+1=xi+d−1.在这个问题中,n=3。
例如,以下序列是AP:[5,10,15]、[3,2,1]、[1,1,1]和[13,10,7]。以下序列不是AP:[1,2,4]、[0,1,0]和[1,3,2]。
您需要回答t个独立的测试用例。
题目概括: 有三个a,b,c,问能不能让这三个数的其中一个数乘上一个正整数使这三个数成等差数列,并且三个数的顺序不能变。
思路
这道题我的思路和官方题解的思路和代码差不多,嘿嘿嘿。
一看到等差数列,就想到了高中数学学的等差数列的性质
- a + c = 2 * d
- c = a + 2 * d
依靠这两个性质就可以求这道题了,存在三种情况:
- a乘上m,那么a * m = c - ( c - b ) * 2
- b乘上m,那么 c - a 一定是 2 的倍数且 b * m = c - ( c - a ) / 2
- c乘上m,那么 c * m = a + 2 * ( b - a )
代码
比赛时提交的代码
#include <bits/stdc++.h>
using namespace std;
int dx[4] = {1, -1, 0, 0};
int dy[4] = {0, 0, 1, -1};
const int N = 1e6 + 10;
typedef long long ll;
typedef pair<int, int> PII;
int t;
int main()
{
cin >> t;
while (t--)
{
int a, b, c;
cin >> a >> b >> c;
if (2 * b == a + c)
{
cout << "YES" << endl;
}
else if ((c - (c - b) * 2) > 0 && (c - (c - b) * 2) % a == 0)
{
cout << "YES" << endl;
}
else if ((c - a) % 2 == 0 && (a + (c - a) / 2) > 0 && (a + (c - a) / 2) % b == 0)
{
cout << "YES" << endl;
}
else if ((a + b - a + b - a) > 0 && (a + b - a + b - a) % c == 0)
{
cout << "YES" << endl;
}
else
{
cout << "NO" << endl;
}
}
return 0;
}
官方题解代码
#include <bits/stdc++.h>
using namespace std;
void solveTest()
{
int a, b, c;
cin >> a >> b >> c;
int new_a = b - (c - b);
if (new_a >= a && new_a % a == 0 && new_a != 0)
{
cout << "YES\n";
return;
}
int new_b = a + (c - a) / 2;
if (new_b >= b && (c - a) % 2 == 0 && new_b % b == 0 && new_b != 0)
{
cout << "YES\n";
return;
}
int new_c = a + 2 * (b - a);
if (new_c >= c && new_c % c == 0 && new_c != 0)
{
cout << "YES\n";
return;
}
cout << "NO\n";
return;
}
int main()
{
ios_base::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int tt;
cin >> tt;
while (tt--)
solveTest();
return 0;
}
C题- 除以2和排列
题目
您将得到一个由n个正整数组成的数组a。您可以对其执行操作。
在一个操作中,您可以用ai替换数组的任何元素⌊ai2⌋, 也就是说,用ai除以2的整数部分(向下舍入)。
查看是否可以多次(可能为0)应用该操作,使数组a成为从1到n的数字排列-也就是说,它包含从1到n的所有数字,每个数字正好一次。
例如,如果a=[1,8,25,2],n=4,那么答案是肯定的。您可以执行以下操作:
将8替换为⌊82⌋=4,那么a=[1,4,25,2]。
将25替换为⌊252⌋=12,那么a=[1,4,12,2]。
将12替换为⌊122⌋=6,那么a=[1,4,6,2]。
将6替换为⌊62⌋=3,那么a=[1,4,3,2]。
==题目概括:==数组中的每个元素可以除无数次2且每次都向下取整,判断是否可以使数组中的每个元素包含从1-n的所有数字且每个数字正好一次。
思路
我做题的思路也是最笨的思路:首先记录数组中每个元素除以一次或多次2后可以形成的1-n内的数字,并且记录1-n中哪个数字出现过,并记录是数组中哪个位置的元素转换而来的。然后开始记录,发现如果有1-n中某个数字没有出现过,或者某个元素只出现过一次但是这个位置已经被其他数字占用了(比如过,数组中某个位置的集合中有4和2,但是其他位置没有出现过2和4,就说明不能实现),出现这两种情况就已经可以说明不能实现了,当时我交了一发,发现WA了第二个测试案例,说明自己还有一些特例没有想到,既然存在0次和存在1次都判断过了,那就说明还有存在多次的没有考虑到,然后我就又想到一个案例,存在一个数出现在两个位置,但是这两个位置都会被其他元素给占用,所以结果为不可能 。比如(5,4,3,1,1)这个例子如果不判断的话就是对的,但是数字2这个元素只能有5和4得来,但是数字5和4只出现了一次,所以没有2,所以保证不了每个数字正好出现一次的结果,然后我开始判断每一个集合大于1的元素,寻找这个数字能否在没有被占用的位置上,如果不能结果为NO,我写的时候是从数字1到数字n开始判断,此处又发现一个bug,题目给的案例(9 8 3 4 2 7 1 5 6)结果为NO,然后我又开始看,发现数字2会占领第四个位置,而数字4就没有位置了,所以应该从数字n到数字1开始找,这样就不会出现这种问题了,最后,终于过了。这道题我从写道改差不多用了一个小时,哎。后来一看题解,发现题解把我所有崎岖的修改过程全想到了,而且更容易理解。
官方题解思路:
让我们按照数组元素值的降序对数组a进行排序。然后,让我们创建一个逻辑数组,其中used[i]的值为true,如果我们已经得到了我们正在寻找的置换的元素i,则值为false。
我们循环遍历数组a的元素,并指定x=ai。只要x超过n或者used[x]为真,我们将x除以2。
如果结果是x=0,那么可以从ai获得的所有数字之前都已经获得了。由于数组a的每个元素都必须产生一个从1到n的新值,因此无法构造答案-输出否。
否则,将used[x]的值赋值为true——这意味着数字x,它是置换的一个元素,我们将从原始数字ai中得到。
处理完数组a的所有元素后,我们可以输出YES。
代码
比赛时提交的代码
#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
#include <stack>
#include <unordered_map>
#include <deque>
#include <vector>
using namespace std;
int dx[4] = {1, -1, 0, 0};
int dy[4] = {0, 0, 1, -1};
const int N = 1e6 + 10;
typedef long long ll;
typedef pair<int, int> PII;
int t;
int n;
int a[60];
unordered_map<int, vector<int>> m;
int main()
{
// freopen("in.in", "r", stdin);
cin >> t;
while (t--)
{
m.clear();
cin >> n;
int f = 1;
for (int i = 1; i <= n; i++)
{
int k;
cin >> k;
while (k)
{
// cout <<k<<" ";
if (k <= n)
{
m[k].push_back(i);
}
k = k / 2;
}
}
memset(a, 0, sizeof a);
for (int i = 1; i <= n; i++)
{
if (m[i].size() == 0)
{
f = 0;
break;
}
else if (m[i].size() == 1)
{
if (a[m[i][0]] != 0)
{
f = 0;
break;
}
a[m[i][0]] = i;
}
}
if (f == 1)
{
for (int i = n; i >= 2; i--)
{
if (m[i].size() < 2)
continue;
int q = 1;
for (int j = 0; j < m[i].size(); j++)
{
if (a[m[i][j]] == 0)
{
// cout << i << " " << m[i][j] << endl;
a[m[i][j]] = i;
q = 0;
break;
}
}
if (q == 1)
{
f = 0;
break;
}
}
if (f == 1)
cout << "YES" << endl;
else
{
cout << "NO" << endl;
}
}
else
{
cout << "NO" << endl;
}
}
return 0;
}
官方题解代码
#include <bits/stdc++.h>
using namespace std;
void solve()
{
int n;
cin >> n;
vector<int> a(n), used(n + 1, false);
for (auto &i : a)
cin >> i;
sort(a.begin(), a.end(), [](int a, int b)
{ return a > b; });
bool ok = true;
for (auto &i : a)
{
int x = i;
while (x > n or used[x])
x /= 2;
if (x > 0)
used[x] = true;
else
ok = false;
}
cout << (ok ? "YES" : "NO") << '\n';
}
int main()
{
ios_base ::sync_with_stdio(false);
cin.tie(nullptr);
int t;
cin >> t;
while (t--)
{
solve();
}
return 0;
}
个人感受
这次比赛我本来也没打算参加,后来看到群里有人提到了这个比赛,发现比赛时间是十点半,太晚了,但是还是参加了(自虐行为,请勿模仿)。比赛时真的使用了浑身解数,通过这次比赛学到了很多东西,比如
sort(a.begin(), a.end(), [](int a, int b)
{ return a > b; });
这个sort自定义排序方式我第一次见到,之前自定义排序都是加一个函数(sort(a,a+n,cmp),然后我试了一下,结构体也能这样排序,瞬间感觉代码量少了很多。
这次比赛时全英文的,对于我来说有点困难,我十二月刚考完四级,还不知道能不能过。光读这三道题大概用了二十分钟左右,每次感觉翻译的不是很好,怕理解错题意就多看了几遍,由于是第一次写,刚开始练怎么交代码都不知道,还找了一会,直接交了,发现选择的语言错了,导致我第一题CE了两次,还是太差劲了我,后来一看排行榜,更受打击,发现前几名都是用了五分钟就A了前三题,而我用了两小时,真的觉得自己要多刷题了。最后看了排名,三万多人参加,我排名八千多,哎。比赛完第二天,终于看到了自己的积分,由0加到了450,内心还是有点开心的,以后多来打打CF,自虐一下。