C. Drop Blocks

只需维护以下三个量即可:

  • \(h_i\) 表示第 \(i\) 个格子被放置方块的总次数(从历史上累加,不随删除而改)
  • \(c_i\) 表示记录“有至少 \(k\) 次放置” 的格子数
  • \(l\) 表示已经执行的“全格子减 1”操作次数(相当于全局基准)。
代码实现
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 0; i < (n); ++i)

using namespace std;

int main() {
    int n, q;
    cin >> n >> q;
    
    vector<int> h(n), c(1e6);
    c[0] = n;
    int l = 0;
    rep(qi, q) {
        int type, x;
        cin >> type >> x;
        if (type == 1) {
            --x;
            h[x]++;
            c[h[x]]++;
            if (c[l+1] == n) l++;
        }
        else {
            cout << c[l+x] << '\n';
        }
    }
    
    return 0;
}

D. Adjacent Distinct String

统计每个字母频次,若最大频次大于 \(\lceil\frac{n}{2}\rceil\) 则无解;否则按频次从大到小依次把字符填入结果串的下标 \(0,2,4,\cdots\),满了再从 \(1,3,5,\cdots\) 填入,能保证相邻字符不同。

感觉这题比上一题简单

代码实现
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 0; i < (n); ++i)

using namespace std;

void solve() {
    string s;
    cin >> s;
    int n = s.size();
    
    vector<int> cnt(26);
    for (char c : s) cnt[c-'a']++;
    
    if (ranges::max(cnt) > (n+1)/2) {
        puts("No");
        return;
    }
    
    puts("Yes");
    
    vector<pair<int, char>> cs;
    rep(i, 26) cs.emplace_back(cnt[i], 'a'+i);
    sort(cs.begin(), cs.end());
    
    string t;
    for (auto [num, c] : cs) t += string(num, c);
    
    string ans = s;
    rep(p, 2) {
        for (int i = p; i < n; i += 2) {
            ans[i] = t.back(); t.pop_back();
        }
    }
    cout << ans << '\n';
}

int main() {
    int t;
    cin >> t;
    
    while (t--) solve();
    
    return 0;
}

E. Select from Subtrees

因为糖果可区分且松鼠仅在其子树内选择,所以每只松鼠的决策是独立的,那么就可以用乘法原理

\(R_v\) 表示以点 \(v\) 为根的子树里剩余可用的糖果数

答案就是 \(\prod\limits_{v=1}^N \dbinom{R_v}{D_v}\)

代码实现
#include <bits/stdc++.h>
#include <atcoder/all>
using namespace atcoder;
#define rep(i, n) for (int i = 0; i < (n); ++i)

using namespace std;
using ll = long long;
using mint = modint998244353;

mint comb(ll n, int k) {
    mint a = 1, b = 1;
    rep(i, k) a *= n-i, b *= i+1;
    return a/b;
}

int main() {
    int n;
    cin >> n;
    
    vector<vector<int>> to(n);
    for (int i = 1; i < n; ++i) {
        int p;
        cin >> p;
        --p;
        to[p].push_back(i);
    }
    
    vector<ll> c(n), d(n);
    rep(i, n) cin >> c[i];
    rep(i, n) cin >> d[i];
    
    mint ans = 1;
    auto dfs = [&](auto& f, int v) -> ll {
        ll num = c[v];
        for (int u : to[v]) num += f(f, u);
        ans *= comb(num, d[v]);
        num -= d[v];
        return num;
    };
    dfs(dfs, 0);
    
    cout << ans.val() << '\n';
    
    return 0;
}

F. -1, +1

首先,处理“严格递增”这一约束。令 \(B_i = A_i + (N - i)\)
可以发现:$$A_i < A_{i+1} \iff A_i + (N - i) \le A_{i+1} + (N - (i + 1)) \iff B_i \le B_{i+1}$$经过转化,目标变为了使序列 \(B\) 非降。同时,原操作(\(A_i-1, A_{i+1}+1\))等价于在 \(B\) 序列中将值从左向右迁移,且总和保持不变。

由于操作只能将值从左向右移动,若设目标序列为 \(D\),则必须满足对于任意 \(k\),前缀和满足 \(\sum\limits_{i=1}^k B_i \geqslant \sum\limits_{i=1}^k D_i\)。在这种约束下,从 \(B\) 变到 \(D\) 的最小操作次数等于前缀和之差的累加:$$\text{Cost} = \sum_{k=1}^{N} \left( \sum_{i=1}^k B_i - \sum_{i=1}^k D_i \right)$$为了使这个代价最小,我们需要找到一个非降序列 \(D\),使得它的前缀和尽可能地“接近” \(B\) 的前缀和。在几何直观上,这等价于求 \(B\) 序列前缀和序列的下凸壳。

我们可以利用单调栈维护一组“块”,每个块包含该区间的数值总和和元素个数:

  • 合并策略:当新加入一个块时,如果前一个块的平均值大于当前块的平均值,说明为了满足非降且代价最小,必须将前一区块的值“压”向后一区块。此时将两个块合并。
  • 整数均衡分配:由于 \(B\) 必须是整数序列,对于一个总和为 \(S\)、长度为 \(W\) 的块,最优的非降排布是将较小的值 \(\lfloor \frac SW \rfloor\) 放在左侧,较大的值 \(\lceil \frac SW \rceil\) 放在右侧。
代码实现
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 0; i < (n); ++i)

using namespace std;
using ll = long long;

void solve() {
    int n;
    cin >> n;
    
    vector<int> a(n);
    rep(i, n) cin >> a[i];
    rep(i, n) a[i] += n-i;
    
    vector<pair<ll, int>> s;
    rep(i, n) {
        ll b = a[i], w = 1;
        while (s.size()) {
            auto [nb, nw] = s.back();
            if ((nb+nw-1)/nw <= b/w) break;
            b += nb; w += nw;
            s.pop_back();
        }
        s.emplace_back(b, w);
    }
    
    vector<ll> d;
    for (auto [b, w] : s) {
        rep(i, w) d.push_back(b/w + (w-i <= b%w)); 
    }
    
    ll ans = 0, sum = 0;
    rep(i, n) {
        sum += a[i]-d[i];
        ans += sum;
    }
    
    cout << ans << '\n';
}

int main() {
    int t;
    cin >> t;
    
    while (t--) solve();
    
    return 0;
}