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] != -1vis[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;
}

T7:炼金工坊