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;
}
浙公网安备 33010602011771号