CF Round #702 Div3 解题补题报告
A题 Dense Array (签到)
给定一个数列 \(\{a_n\}\) ,你可以在数列中插入一些数字,问至少插多少,可以使得任意相邻两项的商(大的除小的)小于等于 \(2\) 。
\(2 \leq n \leq 50,1 \leq a_i \leq 50\)
显然的,每次找相邻两项,然后直接硬加(对小的那个不断乘 \(2\) )。这个数据规模很小,所以不需要任何优化,复杂度 \(O(n\log{a_i})\) 。
#include<bits/stdc++.h>
using namespace std;
const int N = 60;
int n, a[N];
int calc(int x, int y) {
if (x > y) swap(x, y);
int cnt = 0;
while (2 * x < y) cnt++, x *= 2;
return cnt;
}
void solve() {
scanf("%d", &n);
for (int i = 1; i <= n; ++i)
scanf("%d", &a[i]);
int ans = 0;
for (int i = 1; i < n; ++i)
ans += calc(a[i], a[i+1]);
printf("%d\n", ans);
}
int main()
{
int T;
scanf("%d", &T);
while (T--) solve();
return 0;
}
B题 Balanced Remainders (分类讨论)
现在有一个长度为 \(n\) 的数列 \(\{a_n\}\) 。现在每次可以进行一次操作,将某一项加 \(1\) 。问至少需要多少次,可以使得数列中对 \(3\) 取模后为 \(0\) , \(1\) , \(2\) 的项的数量相等。
\(3\leq n \leq 30000\) ,保证 \(n\) 可以被 \(3\) 整除
直接从前到后扫一遍,然后将这三类的数量分别记为 \(c_0,c_1,c_2\)。
现在,我们需要最少步数从 \((c_0,c_1,c_2)\) 变为 \((\frac{n}{3},\frac{n}{3},\frac{n}{3})\) 。
这题就是铁分类讨论,没啥好说的,讨论就完事了。
#include<bits/stdc++.h>
using namespace std;
const int N = 30010;
int n, a[N];
int solve()
{
scanf("%d", &n);
for (int i = 1; i <= n; ++i)
scanf("%d", &a[i]);
int c0 = 0, c1 = 0, c2 = 0;
for (int i = 1; i <= n; ++i)
switch (a[i] % 3) {
case 0 : c0++; break;
case 1 : c1++; break;
case 2 : c2++; break;
}
c0 -= (n/3), c1 -= (n/3), c2 -= (n/3);
int ans = 0;
if (c0 == 0)
if (c1 >= 0) return c1;
else return 2 * (-c1);
if (c0 > 0)
c1 += c0, ans += c0, c0 = 0;
else if (c0 < 0)
c2 -= (-c0), ans += (-c0), c0 = 0;
if (c1 >= 0) return ans + c1;
else return ans + 2 * (-c1);
}
int main()
{
int T;
scanf("%d", &T);
while (T--)
printf("%d\n", solve());
return 0;
}
C题 Sum of Cubes (枚举优化)
给定正整数 \(x\),问是否存在正整数 \(a,b\) ,使得 \(a^3+b^3=x\) 。
\(1\leq x \leq 10^{12}\)
显然 \(a^3,b^3 < a^3+b^3=x \leq 10^{12}\) ,所以 \(a,b <10^4\)
直接枚举?说实话,如果只有一组数据,说不定能卡过去,但是这题有多组数据,我就不冒这个险了(逃
其实我们不妨设 \(a[i]=i^3\) ,那么问题就变成了另一个:
是否存在 \(1\leq i,j \leq 10000\),使得 \(a_i+a_j=x\) ?
这题是不是感觉很熟悉?链接
老题目了,就是通过先 \(Hash\) 一遍,以空间换时间,将复杂度从 \(O(n^2)\) 压到 \(O(n)\) 。咋哈希?直接 \(map\) 就完事了。
#include<bits/stdc++.h>
using namespace std;
#define LL long long
map<LL, LL> Hash;
inline LL f (LL x) { return x * x * x; }
bool solve() {
LL x;
scanf("%lld", &x);
for (LL a = 1; f(a) < x; ++a)
if (Hash.find(x - f(a)) != Hash.end())
return true;
return false;
}
int main()
{
for (LL i = 1; i <= 10000; ++i)
Hash[f(i)] = i;
int T;
scanf("%d", &T);
while (T--)
puts(solve() ? "YES" : "NO");
return 0;
}
当然,也可以直接调用 \(\text{cmath}\) 里面的函数,判断 \(x-a^3\) 是不是立方数,由于浮点误差,我就不写了(逃
D题 Permutation Transformation (递归)
给定一个大小为 \(n\) 的排列 \(\{a_n\}\) ,尝试构造一棵二叉树,具体步骤建议看原题(我懒得翻译了)
\(n \leq 100\)
递归建树,有手就行,复杂度 \(O(n\log n)\)。
(如果 \(n\) 的复杂度达到 \(10^6\) 甚至 \(10^7\) 级别的话,那最好先用数据结构(例如 \(ST\) 表)预处理一下 \(RMQ\) (话说我感觉这玩意复杂度也是 \(O(n \log n)\) 的))
#include<bits/stdc++.h>
using namespace std;
const int N = 110;
int n, a[N], d[N];
void build(int l, int r, int depth) {
if (l > r) return;
int k = 0;
for (int i = l; i <= r; ++i)
if (a[k] < a[i]) k = i;
d[k] = depth;
build(l, k - 1, depth + 1);
build(k + 1, r, depth + 1);
}
void solve()
{
//init
memset(d, 0, sizeof(d));
//input
scanf("%d", &n);
for (int i = 1; i <= n; ++i)
scanf("%d", &a[i]);
//build
build(1, n, 0);
//output
for (int i = 1; i < n; ++i)
printf("%d ", d[i]);
printf("%d\n", d[n]);
}
int main()
{
int T;
scanf("%d", &T);
while (T--) solve();
return 0;
}
E题 Accidental Victory (贪心)
给定 \(n\) 位玩家,每位玩家初始代币数量为 \(a_i\) 。
每次比赛选择两位剩下来的玩家,代币多者获胜,并且夺走败者的代币(代币数目一样则随机决定胜负)。最后场面下剩下来的人就会成为胜者。
现在我们想知道,哪些玩家可能成为胜者?
\(1\leq n \leq 2*10^5,1 \leq a_i \leq 10^9\)
有一个显然的策略:先将所有玩家按照代币数量从小到大排列,然后从前向后扫一遍即可,不停尝试吞,复杂度 \(O(n \log n)\) 。(记得开 \(\text{long long}\))
#include<bits/stdc++.h>
using namespace std;
#define LL long long
const int N = 200010;
int n;
struct node {
LL val;
int id;
bool operator < (const node &rhs) const {
return val < rhs.val;
}
}a[N];
int t[N];
void solve()
{
//input
scanf("%d", &n);
for (int i = 1; i <= n; ++i)
scanf("%lld", &a[i].val), a[i].id = i;
//solve
sort(a + 1, a + n + 1);
LL ans = a[1].val, tmp = a[1].val;
for (int i = 2; i <= n; ++i) {
if (tmp < a[i].val) ans = a[i].val;
tmp += a[i].val;
}
int cnt = 0;
for (int i = 1; i <= n; ++i)
if (a[i].val >= ans) t[++cnt] = a[i].id;
sort(t + 1, t + cnt + 1);
//output
printf("%d\n", cnt);
for (int i = 1; i < cnt; ++i)
printf("%d ", t[i]);
printf("%d\n", t[cnt]);
}
int main()
{
int T;
scanf("%d", &T);
while (T--) solve();
return 0;
}
F题 Equalize the Array (排序,离散化,二分)
当一个数列中,每个元素出现的次数相等,均为 \(C\) 次,那么这个数列就是完美的。
给定一个长度为 \(n\) 的数列 \(\{a_n\}\) ,问至少需要去除多少元素,可以使得这个数列变成完美的?
\(1\leq n \leq 2*10^5,1 \leq a_i \leq 10^9\)
不管咋样,先排个序再说吧。
很显然,我们需要统计每个元素出现的个数。考虑到这个规模,我们必须离散化或者用 \(map\) 。
离散化之后,不妨记 \(cnt_x\) 为 \(x\) 出现的次数,那么,对于 \(C\) ,我们需要做到:
- 将出现次数不足 \(C\) 次的元素删除
- 将出现次数超过 \(C\) 次的元素削减到 \(C\) 次
那么总次数为 $\sum\limits_{cnt_x < C} cnt_x + \sum\limits_{cnt_x \geq C} (cnt_x - C) $ ,优化一下,也就是 \(\sum cnt_x - \sum\limits_{cnt_x \geq C} C\)
那么我们可以直接对 \(cnt\) 数组排序,然后每次二分即可
总复杂度 \(O(n \log n)\) ,小细节多的一
#include<bits/stdc++.h>
using namespace std;
#define LL long long
const int N = 200010;
int n, a[N], m, b[N];
void discrete() {
sort(a + 1, a + n + 1);
m = 0;
for (int i = 1; i <= n; ++i)
if (i == 1 || a[i] != a[i-1])
b[++m] = a[i];
}
int query(int x) {
return lower_bound(b + 1, b + m + 1, x) - b;
}
int cnt[N];
LL sum[N];
void solve()
{
//读入
scanf("%d", &n);
for (int i = 1; i <= n; ++i)
scanf("%d", &a[i]);
//离散化
discrete();
memset(cnt, 0, sizeof(int) * (m + 1));
for (int i = 1; i <= n; ++i)
++cnt[query(a[i])];
//求解
sort(cnt + 1, cnt + m + 1);
cnt[m + 1] = 1e9 + 10;
for (int i = 1; i <= m; ++i)
sum[i] = sum[i - 1] + cnt[i];
LL ans = 1e9 + 10;
for (LL C = 0; C <= n; ++C) {
int i = lower_bound(cnt + 1, cnt + m + 1 + 1, C) - cnt;
ans = min(ans, sum[m] - (m - i + 1) * C);
}
printf("%lld\n", ans);
}
int main()
{
int T;
scanf("%d", &T);
while (T--) solve();
return 0;
}
G题 Old Floppy Drive (前缀和,单调性维护)
有点不太会翻译,告辞
不管咋样,前缀和肯定是要维护一下的。
但是吧,如果每个点上面的数都是正数,那我们直接每次二分一下就好了,并不是很难,但是现在有负数就离谱。
实际上,我们只需要用一个栈来维护单调性,只保存其中单调递增的就行了(反正减的也没用
然后在这上面二分就好了(逃
总体步骤如下:
- 维护前缀和 \(s\)
- 在前缀和里面提取单调增部分组成新数列 \(val\) ,并记录下对应 \(id\)
- 判定无解的情况
- 无法组成 \(val\) 数组(全都 \(NM\) 是负数,还不停减)
- \(x\) 大于 \(val\) 最大值,但是 \(s_n \leq 0\) ,导致最后累计和永远不可能达到 \(x\)
- 当 \(x\) 大于 \(val\) 中最大值时,不断减去 \(s_n\) 并统计答案(必须用除法优化,不能不停的减)
- 当 \(x\) 小于等于 \(val\) 中最大值的时候,对 \(val\) 进行二分,找出对应的坐标 \(id\) 并加上去
小细节真的多的一,这对着数据都调了大半天就离谱
#include<bits/stdc++.h>
using namespace std;
#define LL long long
const int N = 200010;
int n, m;
LL a[N], s[N];
//
int cnt;
LL val[N];
int id[N];
//
LL Query(LL x) {
if (cnt == 0 || (x > val[cnt] && s[n] <= 0)) return -1;
LL ans = -1;
if (x > val[cnt]) {
LL t = (x - val[cnt] + s[n] - 1) / s[n];
ans += n * t, x -= s[n] * t;
}
int k = lower_bound(val + 1, val + cnt + 1, x) - val;
ans += id[k];
return ans;
}
void solve()
{
//input
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; ++i)
scanf("%lld", &a[i]);
//pre
cnt = 0;
for (int i = 1; i <= n; ++i) {
s[i] = s[i - 1] + a[i];
if (s[i] > val[cnt])
id[++cnt] = i, val[cnt] = s[i];
}
//solve
for (int i = 1; i <= m; ++i) {
LL x;
scanf("%lld", &x);
printf("%lld ", Query(x));
}
puts("");
}
int main()
{
int T;
scanf("%d", &T);
while (T--) {
solve();
}
return 0;
}