ABC464 A-F 题解

https://atcoder.jp/contests/abc464/tasks

B - Crop

只需要用变量记录一下非空行、列的开始和结束。

#include <bits/stdc++.h>
using namespace std;
string s[55];
int p[55][55];
int h, w, hl, hr, wl, wr;
int main() {
    cin >> h >> w;
    for (int i = 0; i < h; ++i)
        cin >> s[i];
    bool flag = false;
    for (int i = 0; i < h; ++i) {
        for (int j = 0; j < w; ++j)
            if (s[i][j] == '#') { flag = true, wl = i; break; }
        if (flag) break;
    }
    flag = false;
    for (int i = h - 1; i >= 0; --i) {
        for (int j = 0; j < w; ++j)
            if (s[i][j] == '#') { flag = true, wr = i; break; }
        if (flag) break;
    }
    flag = false;
    for (int i = 0; i < w; ++i) {
        for (int j = 0 ; j < h; ++j)
            if(s[j][i] == '#') { flag = true, hl = i; break; }
        if (flag) break;
    }
    flag = false;
    for (int i = w - 1; i >= 0; --i) {
        for (int j = 0 ; j < h; ++j)
            if(s[j][i] == '#') { flag = true, hr = i; break; }
        if (flag) break;
    }
    for (int i = wl; i <= wr; ++i) {
        for (int j = hl; j <= hr; ++j) cout << s[i][j];
        cout << endl;
    } 
    return 0;
}

C - Plumage Palette

使用桶维护每天每种颜色鸟的数量。当某种颜色出现或消失的时候更新颜色总数。第一天特殊处理。

#include <bits/stdc++.h>
using namespace std;
const int N = 3e5 + 5;
vector<pair<int, int>>p[N];
int n, m, cnt[N];
int main() {
    cin >> n >> m;
    for (int i = 1; i <= n; ++i) {
        int d;
        pair<int, int> t;
        cin >> t.first >> d >> t.second;
        p[d].push_back(t);
        if (d == 1) cnt[t.second]++;
        else cnt[t.first]++;
    }
    int tmp = 0;
    for (int i = 1; i <= n; ++i) if (cnt[i]) tmp++;
    cout << tmp << endl;
    for (int i = 2; i <= m; ++i) {
        for (int j = 0; j < p[i].size(); ++j) {
            cnt[p[i][j].first]--;
            if (cnt[p[i][j].first] == 0) tmp--;
            if (cnt[p[i][j].second] == 0) tmp++;
            cnt[p[i][j].second]++;
        } cout << tmp << endl;
    }
    return 0;
}

D - Celester

题意简述

给出一个只包含 RS 两种字符的字符串 \(S\), 可花费 \(X_i\) 的代价修改第 \(i\) 位使之反转。修改结束后,若第 \(i\) 位为 R 且第 \(i + 1\) 位为 S 则获得 \(Y_i\) 的得分。报告得分可能的最大值。

分析

注意到花费和得分仅仅取决于当前的状态。考虑 \(DP\).

#include <bits/stdc++.h>
#define int long long 
using namespace std;
const int N = 2e5 + 5;
int dp[N][2],X[N],Y[N];
signed main() {
    int t;
    cin >> t;
    while (t--) {
        memset(dp, 0, sizeof(dp));
        string s;
        int n;
        cin >> n >> s;
        for (int i = 1; i <= n; ++i) cin >> X[i];
        for (int i = 1; i < n; ++i) cin >> Y[i];
        dp[1][0] = 0;
        dp[1][1] = -X[1];
        for (int i = 2; i <= n; ++i){
            dp[i][0] = max(dp[i - 1][0] + (s[i - 2] == 'R' && s[i - 1] == 'S') * Y[i - 1],
            dp[i - 1][1] + (s[i - 2] == 'S' && s[i - 1] == 'S') * Y[i - 1]);
            dp[i][1] = max(dp[i - 1][0] + (s[i - 2] == 'R' && s[i - 1] == 'R') * Y[i - 1],
            dp[i - 1][1] + (s[i - 2] == 'S' && s[i - 1] == 'R') * Y[i - 1]) - X[i];
        }
        cout << max(dp[n][0], dp[n][1]) << endl;
    }
    return 0;
}

E - Fill-Rect Query

题意简述

有一张表,初始时填满 A. 给出 \(q\) 次操作,每次操作将 \((1,1)\)\((r,c)\) 的区域填满字符 \(X\). 操作结束后输出这张表。

分析

考察暴力的重复运算,发现有的格子是会被多次修改的,并且最终停留在最后一次修改的状态。那么我们可以从后向前进行操作,如此一来只需要修改此后没有修改的格子了。然而在修改之前,我们必须知道哪些是此后没有修改的,扫一遍该区域的复杂度仍然是 \(O(HW)\) 的,无济于事。然而值得注意的是此题所有的修改都是以 \((1,1)\) 为左上端点的,这意味着当我们进行新的操作时,面对的局面总是有如下的形状。

折线

可以发现没有修改的区域总应当是一个矩形被折线分割后的右下的部分。即是说我们只需要从 \((r,c)\) 开始向 \((1,1)\) 填写,遇到已填的就跳至下一行或直接结束操作即可。复杂度 \(O(HW + Q)\).

#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 5;
const int M = 2e5 + 5;
string s[N];
int h, w, q, rc[M][2], cnt;
char ch[M];
int main() {
    cin >> h >> w >> q;
    for (int i = 0; i < h; ++i)
        for (int j = 0; j < w; ++j)
            s[i] += '.';
    for (int i = 1; i <= q; ++i) {
        cin >> rc[i][0] >> rc[i][1] >> ch[i];
    } rc[0][0] = h, rc[0][1] = w, ch[0] = 'A';
    for (int i = q; i >= 0; --i)
        for (int j = rc[i][0] - 1; j >= 0; --j) {
            if (s[j][rc[i][1] - 1] != '.') break;
            for (int k = rc[i][1] - 1; k >= 0; --k)
                if (s[j][k] != '.') break;
                else s[j][k] = ch[i];
        }
    for (int i = 0; i < h; ++i) cout << s[i] << endl;
    return 0;
}

F - Random Vault Heist

思路来源于洛谷题解

题意简述

考虑序列 \(A_1,A_2,...,A_n,\) 随机地排列这 \(n\) 个元素,对于每种排列,定义 \(M\) 为其前若干项和使得恰有 \(M \ge X\). 求 \(M\) 的期望。

分析

考虑拆贡献。对于每一个 \(A_i\). 其对答案的贡献为

\[E_i = A_i\cdot \sum_{S,sum(S)<x,i\notin S}\frac{|S|!(n-|S|-1)!}{n!} \]

那么有

\[\begin{aligned} E &= \sum E_i \\ &= \sum_{i=1}^n A_i\cdot \sum_{S,sum(S)<x,i\notin S}\frac{|S|!(n-|S|-1)!}{n!} \\ &= \sum_{S,sum(S)<x}\frac{|S|!(n-|S|-1)!}{n!}\cdot\left(\sum A_i-sum(S)\right) \end{aligned} \]

考虑枚举 \(S\) 并将 \(sum(S)\) 储存至数组 \(B_{|S|}\) 中并排序后维护其前缀和 \(C_{|S|},\) 这一过程的复杂度是 \(O(n2^n)\) 的。接着对于 \(|S|=1,2,...,n\) 分别用二分法找到其最大的 \(sum(S) \le X\) 所在的位置,读取前缀和后进行计算即可。

然而本题中 \(n = 40,\) 显然会超时。这时我们可以考虑使用 折半搜索。把 \(A\) 分成 \(L\)\(R\) 两个部分分别进行二进制枚举。复杂度降为 \(O(n2^{\frac{n}{2}}).\) 而后对 \(L\)\(R\) 交互产生的贡献进行统计,和 CDQ 分治有些相似。具体地,我们仍然枚举 \(|S|=1,2,...,n,\) 继而枚举可能的拆分方式,即枚举 \(|L| = 0,1,2,...,|S|,\) 而对于确定的 \(|L|\)\(|R|\) 我们只需要在排序后双指针跑一遍即可。这一过程的复杂度大约也是 \(O(n2^\frac{n}{2}).\) 该问题如此便得以解决。

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int M = 998244353, N = 55;
int fact[N] = { 1 }, A[N];
int n, x, sum, ans;
vector<int> B[2][N], C[2][N];
int fsp(int a,int b) {
    if (b == 0) return 1;
    if (b % 2) {
        int t = fsp(a, (b - 1) >> 1);
        return (t * t) % M * a % M;
    } int t = fsp(a, b >> 1);
    return t * t % M;
} inline void f(int l, int r, int len, int p) {
    for (int i = 0; i < 1 << len; ++i) {
        int sum = 0;
        for (int j = 0; j < len; ++j) 
            if ((1 << j) & i) sum += A[l + j];
        if (sum >= x) continue;
        B[p][__builtin_popcount(i)].push_back(sum);
    }
} inline void sot(int p) {
    for (int i = 0; i <= n; ++i)
        sort(B[p][i].begin(), B[p][i].end());
    for (int i = 0; i <= n; ++i) {
        for (auto it : B[p][i]) C[p][i].push_back(it);
        for (int j = 1; j < B[p][i].size(); ++j)
            (C[p][i][j] += C[p][i][j - 1]) %= M;
    }
}
signed main() {
    for (int i = 1; i <= 40; ++i) fact[i] = fact[i - 1] * i % M;
    cin >> n >> x;
    for (int i = 1; i <= n; ++i) { cin >> A[i]; (sum += A[i]) %= M; }
    int m = n >> 1;
    f(1, m, m, 0), f(m + 1, n, n - m, 1);
    sot(0), sot(1);
    for (int s = 0; s < n; ++s) {
        int tot = 0;
        int lm = max(0ll, s - n + m), rm = min(s, m);
        for (int l = lm; l <= rm; ++l) {
            int r = s - l;
            if (B[1][r].empty()) continue;
            int p = B[1][r].size() - 1;
            for (int q = 0; q < B[0][l].size(); ++q) { 
                while (p >= 0 && B[1][r][p] + B[0][l][q] >= x) p--;
                if (p >= 0) {
                    int tmp = B[0][l][q] % M * (p + 1) % M; 
                    int add = ((((p + 1) * sum) % M - tmp - C[1][r][p]) % M + M) % M;
                    (tot += add) %= M;
                } else break;
            } 
        }
        if (tot) (ans += fact[s] * fact[n - s - 1] % M * tot) %= M;
    }
    cout << (ans * fsp(fact[n], M - 2) % M + M) % M << endl;
    return 0;
}
posted @ 2026-07-02 10:43  尚晨月  阅读(18)  评论(0)    收藏  举报