A. Array Balancing
题意:
给两个数组,给两个操作选择。
1:选择一个整数i(我也不知道这个操作有啥实际意义)
2:交换 Ai 和Bi 的值。
使 ∑ 1 n \sum_1^n ∑1n ∣ ( A i − A i − 1 ) ∣ |(A_i-A_{i-1})| ∣(Ai−Ai−1)∣ + ∣ ( B i − B i − 1 ) ∣ |(B_i-B_{i-1})| ∣(Bi−Bi−1)∣最小。
解题思路:数学关系 如果发现 A i > B i A_i>B_i Ai>Bi就换。(把值大的换到一个数组,值小的放一个数组)
换完直接加就行了。
for (int i = 1; i <= n; i++)
if (a[i] > b[i])
swap(a[i], b[i]);
rep(i, 2, n)
sum += abs(a[i] - a[i - 1]) + abs(b[i] - b[i - 1]);
cout << sum << endl;
收获:写这篇博客的时候对于markdown公式的写法收获大于该题。
B. Getting Zero
题意:
给你一个数(注意是数不是数组)
可以有以下两种操作:
- v = ( v + 1 ) m o d 32768 v = (v+1) mod 32768 v=(v+1)mod32768
- v = ( 2 ⋅ v ) m o d 32768 v=(2⋅v)mod32768 v=(2⋅v)mod32768
把这个数字变成零的最小操作次数。
解题思路:
首先32768不是随便的数字是 2 15 2^{15} 215,任何数 X X X乘以 2 15 2^{15} 215再取余 2 15 2^{15} 215都等于 0 0 0.因此首先本题答案最大为: 2 15 2^{15} 215即32768。
后经过验算得知先加后乘是最优的直接暴力跑就完事了。
int solve(int k)
{
int ans = INT_MAX;
for (int i = 0; i <= 15; i++)
{
for (int j = 0; j <= 15; j++)
{
if (((k + i) * (1 << j)) % 32768 == 0)
{
if (i + j < ans)
ans = i + j;
}
}
}
return ans;
}
C. Water the Trees
题意:
有 n n n棵树,有两种操作。
- 奇数天浇水长高1,偶数天2.
- 不浇水。
需要几天树一样高。
解题思路:
贪心思想,所需的高度是max或max+1,其中max是某个树的最大初始高度。我们不需要大于max+1的高度,因为,比如说,如果高度是max+2,我们可以删除一些动作,得到高度为max的答案。同样的事情也适用于所有大于max+1的高度。为什么我们甚至需要max+1的高度呢?在某些情况下(如[1,1,1,1,1,1,2]),高度max+1的答案比高度max的答案更好(在这个特定的例子中,是9对11)。所以我们假设用二进制搜索。让当前的答案是mid。然后让cnt1=⌈mid2⌉是我们可以做的+1操作的数量,cnt2=⌊mid2⌋是我们可以做的+2操作的数量。我们可以贪婪地使用+2操作,然后只需检查+1操作的数量是否足以增长其余的高度。
cin >> n;
for (int i = 0; i < n; i++)
cin >> a[i];
int mx = *max_element(a, a + n);
int ans = LLONG_MAX;
for (int x : {
mx, mx + 1})
{
int cnt1 = 0, cnt2 = 0;
for (int i = 0; i < n; i++)
{
cnt1 += (x - a[i]) % 2;
cnt2 += (x - a[i]) / 2;
}
long long dif = max(0ll, cnt2 - cnt1 - 1) / 3;
cnt1 += dif * 2;
cnt2 -= dif;
ans = min(ans, max(cnt1 * 2 - 1, cnt2 * 2));
if (cnt2 > 0)
{
cnt1 += 2;
--cnt2;
ans = min(ans, max(cnt1 * 2 - 1, cnt2 * 2));
}
}
cout << ans << endl;
D. Progressions Covering
题意:
给你两个数组:一个由n个零组成的数组a和一个由n个整数组成的数组b。
你可以对数组a进行任意次数的操作:选择一个长度为k的子段,并将算术级数1,2,…,k加到这个子段上–即在子段的第一个元素上加1,在第二个元素上加2,以此类推。所选的子段应该在数组a的边界内(即如果所选子段的左边界是l,那么应该满足1≤l≤l+k-1≤n的条件)。注意添加的级数总是1,2,…,k,而不是k,k-1,…,1或其他什么(即子段的最左边的元素总是增加1,第二个元素总是增加2,以此类推)。
问最少多少次,可以使得整个序列中的数,都小于等于0。
解题思路:
倒着找,遍历一下子都减去k看是怎么样的。u示我们需要从当前元素中减去当前存在的进位的值。a是当前存在的进位的数量。d[i]表示将在i+1位置结束的进位的数量(即不会从i位置再向左增加任何东西)。
#include <bits/stdc++.h>
// 代码由偶像jangly赞助
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int n, k;
std::cin >> n >> k;
std::vector<long long> b(n);
for (int i = 0; i < n; i++)
{
std::cin >> b[i];
}
long long ans = 0;
long long u = 0, a = 0;
std::vector<long long> c(n), d(n);
for (int i = n - 1; i >= 0; i--)
{
u += c[i];
a += d[i];
b[i] += u * i + a;
long long v = std::min(i + 1, k);
if (b[i] > 0)
{
long long t = (b[i] + v - 1) / v;
u -= t;
a -= t * (v - i);
ans += t;
if (i - v >= 0)
{
c[i - v] += t;
d[i - v] += t * (v - i);
}
}
}
std::cout << ans << "\n";
return 0;
}
总结:本场edu主要考查了多种操作方式下的贪心思路题,D题还有点不太熟悉,希望后来可以更加完善,但明显能看出来偶像jiangly的思路和官方题解大致上一模一样。很佩服人家都说怎么一眼出来的,以后还是要多加练习。