CF2185H BattleCows 2 题解
这是此博客的第一篇文章,从洛谷上搬了过来,也起到测试作用。
此题有诸多做法,这里提供一种码量、复杂度较小而思维量较大的解法。
注意到当奶牛处理完左侧区间后,右侧的合并操作对哪个位置都是如出一辙的,当且仅当在 \(a_x > sum_{x-1}\) 的位置需要作弊(\(sum\) 是前缀和)。这样的位置至多只有 \(\text{log}A\) 个,因为每次触发条件都会使前缀和翻倍。
因此我们可以先预处理存下这样的位置,并方便地算出每头牛在位置 \(1\) 时的作弊数,记为 \(h\),同时处理出此时需要作弊的位置集合 \(S\)。此时可以断言,这头牛在任意位置的最大作弊数为 \(h+1\)。(与位置 \(1\) 相比唯一能多产生作弊数的地方在于和左侧的合并,向右合并的作弊数一定 \(\leq h\))。
可以分析得到牛根据位置的作弊数分布一开始保持为 \(h\) ,而后保持为 \(h+1\)(长度可以是 \(0\)),最后再递减回 \(0\) 或 \(1\)。
显然若 \(h < k\),该牛的答案直接为 \(n\)。
若 \(h > k\),只会在递减的后缀中有贡献。设正在处理原始位置为 \(i\) 的奶牛,可以直接找到 \(S\) 的倒数第 \(k\) 个位置 \(p\),并有一个简易的分类讨论。
- \(p > i\),将 \(i\) 移动致 \(p\) 时 \(p\) 在 \(i\) 的左边,此时 \(p\) 就是最大的满足条件的后缀。(当移动到比 \(p\) 更小的位置时左侧依然有合并贡献,总贡献会 \(>k\))。
- \(p < i\),将 \(i\) 移动致 \(p\) 时 \(p\) 在 \(i\) 的右边,因为左侧有合并贡献导致此时总贡献为 \(k+1\),所以 \(p+1\) 才是最小的后缀。
最后剩的 \(h=k\) 情况,先加上上述讨论算出的后缀答案,注意这里算出的后缀答案前提是左侧需要合并。而后可以直接分情况二分出最大的满足左侧无需合并的临界点,这个临界点对应的前缀一定都是符合条件的(因为一定 \(\leq h\))。可知这段前缀和后缀一定不交,并且可以不重不漏统计完所有答案。
时间复杂度 \(O(n (\text{log}n + \text{log}A))\)。
void solve() {
int n, k;
std::cin >> n >> k;
std::vector<int> a(n + 1);
for (int i = 1; i <= n; i++) {
std::cin >> a[i];
}
std::vector<i64> sum(n + 1);
for (int i = 1; i <= n; i++) {
sum[i] = sum[i - 1] + a[i];
}
std::vector<int> pos;
for (int i = 1; i <= n; i++) {
if (a[i] > sum[i - 1]) {
pos.push_back(i);
}
}
std::vector<int> ans(n + 1);
for (int i = 1; i <= n; i++) {
std::vector<int> g;
for (int x : pos) {
if (x != i) {
if (a[x] > sum[x - 1] + (x < i) * a[i]) {
g.push_back(x);
}
}
}
if (g.size() < k) {
ans[i] = n;
continue;
}
if (k > 0) {
int p = g.end()[-k];
ans[i] += n - p + 1 - (p < i);
}
if (g.size() == k) {
if (sum[i - 1] >= a[i]) {
ans[i] += std::lower_bound(sum.begin() + 1, sum.end(), a[i]) - sum.begin();
} else {
ans[i] += std::lower_bound(sum.begin() + 1, sum.end(), 2 * a[i]) - sum.begin() - 1;
}
}
}
for (int i = 1; i <= n; i++) {
std::cout << ans[i] << " \n"[i == n];
}
}

浙公网安备 33010602011771号