T1:加下标
最小值最大 \(\to\) 二分答案
最小值等于 \(m\) 可转化为所有数 \(\geqslant m\)
二分 \(m\),找到满足 \(k\) 次操作后,可使所有数都 \(\geqslant m\) 的最大的 \(m\)
$check(m)$ 中就判断:能否在 \(k\) 次操作内使所有数都 \(\geqslant m\),找到使得 \(check(m)\) 为 true
的最大 \(m\) 即为答案
\(check\) 可转化为判断如果要让所有数都 \(\geqslant m\),需要的操作次数是否 \(\leqslant k\)
用贪心法求操作次数
对每个 \(a_i'\) 增加若干个 \(i\) 使其 \(\geqslant m\)
每次 \(check\) 都是对原始 \(a\) 加下标,所有每次要用一个新数组复制 \(a\) 再增加
可以开一个变量 sum
来记录对每个下标进行操作的增量,这样就能减少一重循环
代码实现
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 0; i < (n); ++i)
using namespace std;
using ll = long long;
int main() {
int n, k;
cin >> n >> k;
vector<ll> a(n);
rep(i, n) cin >> a[i];
ll ac = 0, wa = 1e18;
while (abs(ac-wa) > 1) {
ll wj = (ac+wa)/2;
auto ok = [&]{
ll cnt = 0, sum = 0;
rep(i, n) if (a[i]+sum < wj) {
ll t = (wj-a[i]-sum+i)/(i+1);
cnt += t;
sum += t*(i+1);
}
return cnt <= k;
}();
(ok ? ac : wa) = wj;
}
cout << ac << '\n';
return 0;
}
T2:破解Enigma
可以用三重循环枚举所有可能的 \(k\)
check(k)
中用 \(k\) 对所有密文解密,如果原文都是前半 \(=\) 后半,则返回 true
,只要有一个原文前半 \(\neq\) 后半,则返回 false
\(s_i\) 解密后是什么字符?
把 \(A \sim Z\) 对应成 \(0 \sim 25\)
加密就是对应数相加再 \(\%26\)
解密就是密文 \((s_i - \text{'A'})-(k_i-\text{'A'})\) 再 \(\%26\)
得到 \((s_i - k_{i\%3}) \% 26\)
代码实现
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 0; i < (n); ++i)
using namespace std;
int main() {
int n;
cin >> n;
vector<string> C(n);
rep(i, n) cin >> C[i];
auto check2 = [&](string k, string s) {
// 用 k 解密 s 后是否前半 = 后半
int l = s.size();
l /= 2;
rep(i, l) {
if (((s[i]-k[i%3]) - (s[i+l]-k[(i+l)%3])) % 26 != 0) return false;
}
return true;
};
auto check = [&](string k) {
rep(i, n) {
if (!check2(k, C[i])) return false;
}
return true;
};
for (char a = 'A'; a <= 'Z'; ++a) {
for (char b = 'A'; b <= 'Z'; ++b) {
for (char c = 'A'; c <= 'Z'; ++c) {
string k;
k += a; k += b; k += c;
if (check(k)) {
cout << k << '\n';
return 0;
}
}
}
}
return 0;
}
T3:城堡大冒险
暴力枚举+前缀和优化
用 sum[i][j]
表示第 \(i\) 层前 \(j\) 个房间的分数和
\(x = sum[1][\max(a, i)] - sum[1][\min(a, i)-1]\)
\(y = sum[2][\max(i, j)] - sum[2][\min(i, j)-1]\)
\(z = sum[3][\max(j, b)] - sum[3][\min(j, b)-1]\)
\( ans = \max(ans, x+y+z) \)
代码实现
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 1; i <= (n); ++i)
using namespace std;
int sum[4][2005];
int main() {
int n;
cin >> n;
rep(i, 3)rep(j, n) {
int x;
cin >> x;
sum[i][j] = sum[i][j-1]+x;
}
int ans = -2e9;
rep(a, n)rep(i, n)rep(j, n)rep(b, n) {
int x = sum[1][max(a, i)] - sum[1][min(a, i)-1];
int y = sum[2][max(i, j)] - sum[2][min(i, j)-1];
int z = sum[3][max(j, b)] - sum[3][min(j, b)-1];
ans = max(ans, x+y+z);
}
cout << ans << '\n';
return 0;
}
需要砍掉两个点,可以选择砍掉 \(a\) 和 \(b\),可以预处理出第一层中从哪个房间到 \(i\) 号房间的分数和最大,以及第三层中从 \(j\) 号房间走到哪个房间的分数和最大
s1[i]
表示第 \(1\) 层某个房间从左或右走到 \(i\) 号房间的分数和的最大值
s3[j]
表示第 \(3\) 层 \(j\) 号房间从左或右走到某个房间的分数和的最大值
\( s_2 = sum[2][\max(i, j)] - sum[2][\min(i, j)-1] \)
代码实现
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 1; i <= (n); ++i)
using namespace std;
int sum[4][2005], s1[2005], s3[2005];
int main() {
int n;
cin >> n;
rep(i, 3)rep(j, n) {
int x;
cin >> x;
sum[i][j] = sum[i][j-1]+x;
}
memset(s1, 0xcf, sizeof s1);
memset(s3, 0xcf, sizeof s3);
rep(a, n)rep(i, n) {
s1[i] = max(s1[i], sum[1][max(a, i)] - sum[1][min(a, i)-1]);
}
rep(j, n)rep(b, n) {
s3[j] = max(s3[j], sum[3][max(j, b)] - sum[3][min(j, b)-1]);
}
int ans = -2e9;
rep(i, n)rep(j, n) {
int s2 = sum[2][max(i, j)] - sum[2][min(i, j)-1];
ans = max(ans, s1[i]+s2+s3[j]);
}
cout << ans << '\n';
return 0;
}
T4:火炎纹章1
先标记出敌人能走到的格子,用 vis[i][j]
记录格子 \((i, j)\) 敌人是否可以走到
枚举每一个敌人,把当前的敌人所在的位置当做起点进行 \(bfs\),在 \(bfs\) 的过程中把能走到的格子进行标记
然后,从玩家所在的位置出发进行 \(bfs\),在 \(bfs\) 的过程中把玩家能够走到的位置进行标记
用 dis[i][j]
表示格子 \((i, j)\) 玩家是否可以走到
最后,枚举每一个位置 \((i, j)\),统计合法位置的个数为最终答案
合法的位置:玩家可以走到但是敌人是不能走到,即 dis[i][j] != -1
且 vis[i][j] = 0
T5:理想队形
区间dp
记 dp[i][j]
表示剩下区间为 \([i, j]\) 带来的最小相似度
只剩下 \([l, r]\) 时,取 \(h[l]/h[r]\)
取了 \(h[l]\),\(|h[l] - b[n-len]| + dp[l+1][r]\)
取了 \(h[r]\),\(|h[r] - b[n-len]| + dp[l][r-1]\)
\(dp[i][i] = |h[i]-h[n]|\)
代码实现
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 0; i < (n); ++i)
using namespace std;
using ll = long long;
inline void chmin(ll& x, ll y) { if (x > y) x = y; }
int main() {
int n;
cin >> n;
vector<int> h(n);
rep(i, n) cin >> h[i];
vector<int> b(n);
rep(i, n) cin >> b[i];
vector<vector<ll>> dp(n, vector<ll>(n));
rep(i, n) dp[i][i] = abs(h[i]-b[n-1]);
for (int w = 1; w < n; ++w) {
rep(l, n) {
int r = l+w;
if (r >= n) break;
ll now = 1e18;
chmin(now, dp[l+1][r] + abs(h[l]-b[n-1-w]));
chmin(now, dp[l][r-1] + abs(h[r]-b[n-1-w]));
dp[l][r] = now;
}
}
cout << dp[0][n-1] << '\n';
return 0;
}
T6:冲浪
部分分做法:
\(10\) 分做法:
当 \(n = 3\),\(k = 1\) 时:
只有两种情况:
- 将两边浪中其中一个的高度改成中间浪的高度
- 将中间的浪的高度改成两边浪的高度的平均值
代码实现
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 0; i < (n); ++i)
using namespace std;
using ll = long long;
int main() {
int n, k;
cin >> n >> k;
vector<int> h(n);
rep(i, n) cin >> h[i];
ll ans;
if (n == 3 and k == 1) {
ans = min(abs(h[0]-h[1]), abs(h[1]-h[2]));
if (ans > (abs(h[0]-h[2])+1)/2) {
ans = (abs(h[0]-h[2])+1)/2;
}
}
cout << ans << '\n';
return 0;
}
\(40\) 分做法:
暴力枚举修改哪几个海浪
代码实现
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 0; i < (n); ++i)
using namespace std;
using ll = long long;
int main() {
int n, k;
cin >> n >> k;
vector<int> h(n);
rep(i, n) cin >> h[i];
ll ans = 1e18;
vector<bool> vis(n);
auto calc = [&]() {
ll s = 0; int last = 0;
while (vis[last] and last < n) ++last; // 找到第一个未被修改的海浪
for (int i = last+1; i < n; ++i) {
if (vis[i]) continue;
int d = abs(h[i]-h[last]);
ll f = d/(i-last);
if (d%(i-last) > 0) f++;
s = max(s, f);
last = i;
}
ans = min(ans, s);
};
if (k == 1) {
rep(i, n) {
vis[i] = 1;
calc();
vis[i] = 0;
}
}
if (k == 2) {
rep(i, n) {
for (int j = i+1; j < n; ++j) {
vis[i] = vis[j] = 1;
calc();
vis[i] = vis[j] = 0;
}
}
}
if (k == 3) {
rep(i, n) {
for (int j = i+1; j < n; ++j) {
for (int t = j+1; t < n; ++t) {
vis[i] = vis[j] = vis[t] = 1;
calc();
vis[i] = vis[j] = vis[t] = 0;
}
}
}
}
cout << ans << '\n';
return 0;
}
满分做法:
二分答案
注意到预计答案越小,不符合要求的差值,需要修改的数字越多
如何check mid的正确性?
可以考虑dp
记 dp[i]
表示冲到 \(i\) 需要修改的海浪数(\(i\) 不修改)
转移方程:
\( dp[i] = \min\{dp[j] + (i-j)\} \),其中 \(j < i\),\(|h_i - h_j| \leqslant mid*(i-j)\)
初始:\(dp[i] = i-1\)
最后的答案为 \(\min\{dp[i] + n-i\}\)
最小修改次数 \(\leqslant k\) 说明当前的方案是可行的
代码实现
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 0; i < (n); ++i)
using namespace std;
using ll = long long;
const int INF = 21e8;
inline void chmin(int& x, int y) { if (x > y) x = y; }
int main() {
int n, k;
cin >> n >> k;
vector<int> h(n);
rep(i, n) cin >> h[i];
int wa = -1, ac = 0;
rep(i, n-1) ac = max(ac, abs(h[i]-h[i+1]));
while (ac-wa > 1) {
int wj = (ac+wa)/2;
auto ok = [&]{
vector<int> dp(n);
rep(i, n) dp[i] = i;
for (int i = 1; i < n; ++i) {
for (int j = i-1; j >= 0; --j) {
if (abs(h[i]-h[j]) <= (ll)wj*(i-j)) {
chmin(dp[i], dp[j]+(i-j-1));
}
}
}
int res = INF;
rep(i, n) chmin(res, dp[i]+n-1-i);
return res <= k;
}();
(ok ? ac : wa) = wj;
}
cout << ac << '\n';
return 0;
}