比赛链接:
https://codeforces.com/contest/1661
A. Array Balancing
题目大意:
\(a\) 和 \(b\) 两个序列,可以交换 \(a_i\) 和 \(b_i\),问 \(\sum_{i = 1}^{n - 1}(\lvert a_i - a_{i + 1} \rvert + \lvert b_i - b_{i + 1} \rvert)\) 的最小值。
思路:
因为交换了第 \(i\) 位之后,不会影响后面的结果,所以直接计算交换和不交换的最小值就行。
代码:
#include <bits/stdc++.h>
using namespace std;
int T = 1, n;
void solve(){
cin >> n;
long long ans = 0;
vector <int> a(n), b(n);
for (int i = 0; i < n; i ++ ) cin >> a[i];
for (int i = 0; i < n; i ++ ) cin >> b[i];
for (int i = 0; i < n - 1; i ++ )
ans += min( abs(a[i] - a[i + 1]) + abs(b[i] - b[i + 1]), abs(a[i] - b[i + 1]) + abs(b[i] - a[i + 1]) );
cout << ans << "\n";
}
int main(){
cin >> T;
while (T--)
solve();
return 0;
}
B. Getting Zero
题目大意:
对于一个数 \(x\),有两步操作可以进行。
一、\(x\) = (\(x\) + 1) % 32768
二、\(x\) = (\(x\) * 2) % 32768
给了一个序列 \(a\),求出让 \(a_i\) 变成 0 的最小操作次数。
思路:
32768 为 \(2^{15}\),所以每一个数每次乘上 2,最多 15 步就可以变成 0。
直接暴力跑一下这个数直接乘 2 变成 0 的操作次数,或它加上一个小于等于 15 的数后乘 2 变成 0 的操作次数,最小的那个就是答案。
代码:
#include <bits/stdc++.h>
using namespace std;
int n, mod = 32768;
int cal(int x){
int ans = 20;
for (int i = 0; i <= 15; i ++ ){
int t = (x + i) % mod, cnt = i;
while ( t != 0 ){
t = t * 2;
t %= mod;
cnt++;
}
ans = min(ans, cnt);
}
return ans;
}
int main(){
cin >> n;
vector <int> a(n);
for (int i = 0; i < n; i ++ )
cin >> a[i];
for (int i = 0; i < n; i ++ )
cout << cal(a[i]) << " \n"[i == n - 1];
return 0;
}
C. Water the Trees
题目大意:
告诉 \(n\) 棵树的高度,从第一天开始,奇数天可以让某一棵树长 1,偶数天可以让某一棵树长 2,问最少几天可以让所有树的高度相同。
思路:
定义树的初始高度最大值为 \(mx\)。
首先最后的高度一定是 \(mx\),或者 \(mx + 1\)。如果最终高度大于 \(mx + 2\),例如 \(mx + 2\),可以让每棵树的最终高度 -2,那么一定可以少掉几天去生长,即超过 2 都不可能出现最优方案。
接着计算让树长到最终高度需要的奇数的天数的个数及偶数天数的个数,为了实现总天数的最小,应该尽可能让奇数天数和偶数天数接近,所以要让偶数天数减少,奇数天数增加。(偶数天数可以转化为奇数天数,而奇数天数不能转化为偶数天数,例如: 1 1 2,如果最终高度为 2,那么最少花 3 天,而不是两天,两个 1 不能合成一个 2)
一个偶数天数变成两个奇数天数,所以偶数天数减少它们差值的三分之一左右,直接暴力跑三分之一附近的一些值去计算就行。
代码:
#include <bits/stdc++.h>
using namespace std;
#define LL long long
LL T = 1, n;
void solve(){
cin >> n;
vector <LL> h(n);
LL mx = 0, ans = 1e18;
for (int i = 0; i < n; i ++ ){
cin >> h[i];
mx = max(mx, h[i]);
}
for (auto x : {mx, mx + 1}){
LL cnt1 = 0, cnt2 = 0;
for (int i = 0; i < n; i ++ ){
cnt1 += (x - h[i]) % 2;
cnt2 += (x - h[i]) / 2;
}
LL k = max(0LL, (cnt2 - cnt1) / 3);
for (LL i = max(0LL, k - 10); i <= k + 10; i ++ ){
LL p = cnt1 + i * 2, q = cnt2 - i;
ans = min( ans, max(p * 2 - 1, q * 2) );
}
}
cout << ans << "\n";
}
int main(){
ios::sync_with_stdio(false);cin.tie(0);
cin >> T;
while (T--)
solve();
return 0;
}
D. Progressions Covering
题目大意:
一个全部元素为 0 的序列 \(a\) 和给定的一个序列 \(b\),现在可以让一段区间 \(i\) 到 \(i + k - 1,a_i + 1,a_{i + 1} + 2,\dots,a_{i + k - 1} + k\) ,问最少多少次操作后可以所有 \(a_i > b_i\)。
思路:
因为要最小,按照贪心的思路,从右往左来加值,即先通过操作让 \(a_n > b_n\),从右到左一直到 \(a_1 > b_1\)。
对于 \(a_i\),最优的策略肯定是让 \(a_i + k\),在这个过程中 \(a_i\) 的改变会导致前 \(k - 1\) 个元素发生改变,所以对于第 \(i\) 个元素,要记录第 \(i\) 个元素的已经在之前变化了多少。因为第 \(i\) 个元素的变化就是第 \(i + 1\) 个元素的变化减去对这个元素产生的影响的位置的数量,所以只需要在原有的基础上减去一个值就行。
按照上面的逻辑,还要记录有几个位置在对当前这个元素产生了影响,每个位置只对前 \(k - 1\) 个元素产生影响,所以当到达前 \(k\) 个元素时,要将这个影响消去,用一个差分数组去记录即可。
代码:
#include <bits/stdc++.h>
using namespace std;
#define LL long long
LL n, k;
int main(){
ios::sync_with_stdio(false);cin.tie(0);
cin >> n >> k;
vector <LL> b(n + 1), p(n + 1);
LL s = 0, cnt = 0, ans = 0;
for (int i = 1; i <= n; i ++ )
cin >> b[i];
for (LL i = n; i >= 1; i -- ){
s -= cnt;
cnt -= p[i];
b[i] -= s;
if (b[i] <= 0) continue;
LL t = min(i, k), d = (b[i] + t - 1) / t;
cnt += d;
s += d * t;
ans += d;
p[max(0LL, i - k)] += d;
}
cout << ans << "\n";
return 0;
}
E. Narrow Components
题目大意:
思路:
代码: