2024 暑假友谊赛-热身1
2024 暑假友谊赛-热身1
A - 🐓
题意
给出每个点的花费,需要将它转换为 1,求最小花费。
思路
要想把所有数变成 1,那有两种选择,一是直接变成 1,二是将这个数先变成其他某个数,再有那个数继续迭代下去。
到这里,我们应该感觉到了,这与 𝑓𝑙𝑜𝑦𝑑 求最短路的过程一致,所以我们可以跑 𝑓𝑙𝑜𝑦𝑑 求解。
代码
#include<bits/stdc++.h>
using namespace std;
using i64 = long long;
const int N = 2e2 + 10;
int c[N][N], A[N][N];
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int h, w;
cin >> h >> w;
for (int i = 0; i < 10; i ++)
for (int j = 0; j < 10; j ++)
cin >> c[i][j];
for (int i = 1; i <= h; i ++)
for (int j = 1; j <= w; j ++)
cin >> A[i][j];
for (int k = 0; k < 10; k ++)
for (int i = 0; i < 10; i ++)
for (int j = 0; j < 10; j ++)
c[i][j] = min(c[i][j], c[i][k] + c[k][j]);
i64 ans = 0;
for (int i = 1; i <= h; i ++)
for (int j = 1; j <= w; j ++)
ans += c[A[i][j]][1];
cout << ans << '\n';
return 0;
}
B - 🐓🐓
题意
给 n 个数,你可以找任意数 b ,使得 \(|A_1-(b+1)|+\dots+|A_i-(b+i)|+\dots+|A_i-(b+n)|\) 最小。
思路
先写出通项: \(∣A_i−(b+i)∣\)
上式可以转化成:$ ∣(𝐴_𝑖−𝑖)−𝑏∣$
想要这个式子的值小,当然要让 𝑏 尽可能接近 \(𝐴_𝑖−𝑖\) ,所以我们预处理出 \(A_i−i\) 然后排序, b 取中位数时可以使得原式最小,也就是 \(A_{\lfloor\frac{n}{2}\rfloor+1}\)。
代码
#include<bits/stdc++.h>
using namespace std;
using i64 = long long;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
cin >> n;
i64 ans = 0;
vector<i64> a(n + 1);
for (int i = 1; i <= n; i ++) {
cin >> a[i];
a[i] -= i;
}
sort(a.begin() + 1, a.end());
for (int i = 1; i <= n; i ++) {
ans += abs(a[i] - a[n / 2 + 1]);
}
cout << ans << '\n';
return 0;
}
C - 🐓🐓🐓
题意
给一个排列,每次可以推平长度为 k 的区间使其都变为这个区间的最小值,求整个排列相等最少需要推平的次数。
思路
首先无论 𝑘 为多少,只要序列中有 1,那么平推后的结果必然全是 1。
所以只要先平推有 1 的 𝑘 个数区间,剩下还有 𝑛−𝑘 个数。每一次包含一个 1,再包含 𝑘−1 个其他数。这样,那 𝑘−1 个其他数都会变成 1,这个的次数为 \(\lceil\frac{n-k}{k-1}\rceil\),再加上 1,就等于 \(\lceil\frac{n-k+(k-1)}{k-1}\rceil=\lceil\frac{n-1}{k-1}\rceil\)。
代码
#include<bits/stdc++.h>
using namespace std;
using i64 = long long;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, k;
cin >> n >> k;
int pos;
vector<i64> a(n + 1);
for (int i = 1; i <= n; i ++) {
cin >> a[i];
}
int ans = (n - 1 + (k - 2)) / (k - 1);
cout << ans << '\n';
return 0;
}
D - 🐓🐓🐓🐓
题意
将一个序列 A 划分成连续的四段,使得这四段的和的极差最小。
思路
先求前缀和,于是题目转化成从 1∼𝑛−1 中选出三个数 𝑖,𝑗,𝑘 使得 \(𝑠_𝑖,𝑠_𝑗−𝑠_𝑖,𝑠_𝑘−𝑠_𝑗,𝑠_𝑛−𝑠_𝑘\) 的极差最小.
考虑枚举 𝑗 ,那么 𝑖,𝑘 一定是把 1∼𝑗−1 和 𝑗+1∼𝑛 划分得最平衡的分界点, 即使得 \(∣(𝑠_𝑗−𝑠_𝑖)−𝑠_𝑖∣,∣(𝑠_𝑛−𝑠_𝑘)−(𝑠_𝑘−𝑠_𝑗)∣\) 最小的 𝑖,𝑘 。
因为如果不平衡,那么划分出的两段的和的最小值值一定比平衡状态下的最小值更小,最大值比平衡状态大,从而总体的极差可能会变大。
由于 𝑗 是从小到大枚举的,从而 \(𝑠_𝑗−𝑠_𝑖\) 变大、 \(𝑠_𝑘−𝑠_𝑗\) 变小,所以最优化划分点 𝑖,𝑘 一定是单调不降的。
代码
#include<bits/stdc++.h>
using namespace std;
using i64 = long long;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
cin >> n;
vector<i64> a(n + 1), pre(n + 1);
for (int i = 1; i <= n; i ++) {
cin >> a[i];
pre[i] = pre[i - 1] + a[i];
}
i64 ans = 1 << 30;
i64 ma, mi, t1=1, t2=3;
for (int i = 2; i < n; i ++) {
while(t1 < i && abs(pre[t1]-(pre[i]-pre[t1]))>abs(pre[t1+1]-(pre[i]-pre[t1+1])))
t1 ++;
while(t2<n &&abs((pre[t2]-pre[i])-(pre[n]-pre[t2]))>abs((pre[t2+1]-pre[i])-(pre[n]-pre[t2+1])))
t2 ++;
ma = max({pre[t1], pre[i] - pre[t1], pre[t2] - pre[i], pre[n] - pre[t2]});
mi = min({pre[t1], pre[i] - pre[t1], pre[t2] - pre[i], pre[n] - pre[t2]});
ans = min(ans, ma - mi);
}
cout << ans << '\n';
return 0;
}
E - 🐓🐓🐓🐓🐓
题意
给你一个区间,你需要找出该区间任意一个数,这个数需满足其数位上最大值减去最小值的值最小。
思路
貌似不用数位 dp 也可以做,不过为了练习下我还是坚持用数位 dp。
考虑数位 dp。
当 l 和 r 位数不同时,一定可以找出全为 9 的数。
相同时考虑数位 dp,设计 \(dp_{Len,Max,Min,Up,Down}\),分别代表长度,最大值,最小值,上界,下界。
递归中用了一个 of 参数,是用来计算具体的数的,题目要求输出具体方案,所以用了另外一个数组存储。
代码
#include<bits/stdc++.h>
using namespace std;
using i64 = long long;
i64 dp[25][20][20][2][2];
i64 num[25][20][20][2][2];
void solve() {
i64 l, r;
cin >> l >> r;
vector<int> U(20, -1), D(20, -1);
D[1] = l % 10;
i64 lenl = 1, offset = 1, tmp = l / 10, lenr = 1;
while (tmp) {
offset *= 10;
D[++lenl] = tmp % 10;
tmp /= 10;
}
tmp = r / 10, U[1] = r % 10;
while (tmp) {
U[++lenr] = tmp % 10;
tmp /= 10;
}
if (lenl != lenr) {
cout << string(lenr - 1, '9') << '\n';
return ;
}
reverse(D.begin() + 1, D.begin() + lenl + 1);
reverse(U.begin() + 1, U.begin() + lenr + 1);
memset(dp, -1, sizeof dp);
memset(num,0,sizeof num);
auto dfs = [&](auto & self, int len, i64 of, int Max, int Min, bool up, bool down)->i64 {
if (len == lenr + 1) return Max - Min;
if (~dp[len][Max][Min][up][down])
return dp[len][Max][Min][up][down];
i64 Mans = 10;
for (int i = (down ? D[len] : 0); i <= (up ? U[len] : 9); i ++) {
i64 res = self(self, len + 1, of / 10, max(Max, i), min(Min, i), up & (i == U[len]), down & (i == D[len]));
i64 val = num[len + 1][max(Max, i)][min(Min, i)][up & (i == U[len])][down & (i == D[len])];
if (res < Mans) {
Mans = res;
num[len][Max][Min][up][down] = i * of + val;
}
}
return dp[len][Max][Min][up][down] = Mans;
};
dfs(dfs, 1, offset, 0, 10, 1, 1);
cout << num[1][0][10][1][1] << '\n';
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t;
cin >> t;
while (t--)
solve();
return 0;
}
F - 🐓🐓🐓🐓🐓🐓
题意
有 n 个格子,k 个空调,k 个空调所在的位置有初始温度,空调会以公差为 1 的温度影响两边的格子,同个格子被多个温度影响取最小值,问你所有格子的温度。
思路
记录一个温度指针,从左往右和从右往左分别扫一次,每次更新最小温度,并且每经过一个格子增加 1 度。
代码
#include<bits/stdc++.h>
using namespace std;
using i64 = long long;
void solve() {
int n, k;
cin >> n >> k;
vector<int> ans(n + 1, 1 << 30), air(k + 1), t(k + 1);
for (int i = 1; i <= k; i ++)
cin >> air[i];
for (int i = 1; i <= k; i ++) {
cin >> t[i];
ans[air[i]] = t[i];
}
int now = 1 << 30;
for (int i = 1; i <= n; i ++) {
now = min(++now, ans[i]);
ans[i] = min(now, ans[i]);
}
now = 1 << 30;
for (int i = n; i >= 1; i --) {
now = min(++now, ans[i]);
ans[i] = min(now, ans[i]);
}
for (int i = 1; i <= n; i ++)
cout << ans[i] << " \n"[i == n];
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t;
cin >> t;
while (t--)
solve();
return 0;
}
G - 🐓🐓🐓🐓🐓🐓🐓
题意
给你一个字母按钮的出招顺序,每个按钮对应一个 \(A_i\) 伤害,但对于同一个按钮不能点击超过 k 次,问你可以跳过一些按钮顺序使得造成的最大伤害为多少。
思路
枚举按钮顺序,对于同一个按钮,可以先存进堆里,碰到不同按钮或者到最后了,从堆里取出 k 个并清空堆即可。
代码
#include<bits/stdc++.h>
using namespace std;
using i64 = long long;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, k;
cin >> n >> k;
vector<i64> a(n);
for (int i = 0; i < n; i ++) {
cin >> a[i];
}
string s;
cin >> s;
priority_queue<i64> Q;
i64 ans = 0;
for (int i = 0; i < n; i ++) {
Q.push(a[i]);
if (i == n - 1 || i < n - 1 && s[i] != s[i + 1]) {
int cnt = 0;
while (cnt++ < k && Q.size()) {
ans += Q.top();
Q.pop();
}
while (Q.size()) Q.pop();
}
}
cout << ans << '\n';
return 0;
}
H - 🐓🐓🐓🐓🐓🐓🐓🐓
题意
给你一个数 L,要求你构造一个 n 个点的有向图,且从 1 到 n 恰好有 L 条权值为 0 到 L-1 的路径。
思路
将 L 拆分成二进制,取最高位为 k ,从 i=1 到 k 每次构造 \(i\rightarrow i+1\) 权值为 0 和 \(2^{i-1}\) 的两条边,这样就能凑出 \([0,2^k-1]\) 的所有路径,如果 L 二进制中除了最高位还有其他位有 1 ,则从 i 连一条到 n 号点的路径,权值为抹掉这一位及以下所有数的值后的值,例如 L = 25 时,25 = 16 + 8 + 1,其二进制为 11001,构造完 1 到 5号点的边后只能凑出 \([0,2^4-1]\) 的,对于第 0 位的 1 ,还需要构造一条 1 到 5 权值为 \(24_{(11000)}\) 的边,对于第 3 位的 1 ,还需要构造一条 4 到 5 权值为 \(16_{(10000)}\) 的边。
代码
#include<bits/stdc++.h>
using namespace std;
using i64 = long long;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int k = 0, L;
cin >> L;
while ((1 << (k + 1)) <= L)
k ++;
int n = k + 1;
cout << n << ' ' << 2 * k + __builtin_popcount(L) - 1 << '\n';
for (int i = 1; i <= k; i ++) {
cout << i << ' ' << i + 1 << " 0" << '\n';
cout << i << ' ' << i + 1 << ' ' << (1 << (i - 1)) << '\n';
}
for (int i = 0; i < k; i ++) {
if ((L >> i) & 1) {
cout << i + 1 << ' ' << n << ' ' << (L & ~((1 << (i + 1)) - 1)) << '\n';
}
}
return 0;
}