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; }
本文作者:Ke_scholar
本文链接:https://www.cnblogs.com/Kescholar/p/18299284
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步