P11455 [USACO24DEC] Cowdepenence G 题解

这题根号分治的想法还是很好的,当然整体二分也是很好的方法 @qwert

首先我们需要暴力去求出这个答案,这里发现每一个点只有和与它标号相等的数有关,直接放在一个 vector 里即可,每一次把每一个扫一遍,\(O(n^2)\)

来切部分分,首先标号不超过 10 次,可以知道对于每一个数值,\(x\) 的大小至多就 10 种。而且不一定每一个都能达到,可以对每一个 \(x\) 用二分找到他的右端点,区间增加一个值,前缀和即可。

这样的话就可以直接合并这两个。

先分析一下时间复杂度,一次暴力是 \(O(c)\) \(c\) 是 vector 的长度。假设分治分割点为 \(B\) 。 总共的时间复杂度就是 \(O(cB)\)

部分分这个方法的话 \(O(n/B\log nc)\) 这里的 \(c\) 是因为二分的 check 时间复杂度。

对于每一个值求和就是 \(O(nB+n^2\log n/B)\) 当这个 B = \(\sqrt{n\log n}\) 时取到最优。

#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 5;
int n, B, a[N], pre[N]; vector <int> e[N];
void change(int l, int r, int k) { pre[l] += k; pre[r + 1] -= k; }
int calc(int c, int x) {
    int la = -1e9, ans = 0;
    for (int t : e[c]) if (t - la > x) ans++, la = t;
    return ans;
}
//计算这个数在这个点 x 是否可行.
signed main() {
    cin >> n; B = sqrt(n * __lg(n));
    for (int i = 1; i <= n; i++) cin >> a[i], e[a[i]].push_back(i);
    for (int i = 1; i <= n; i++) if (e[i].size()) {
        for (int x = 1; x <= B; x++) change(x, x, calc(i, x));
        for (int x = B + 1; x <= n; ) {
            int l = x, r = n, ans = l, t = calc(i, x);
            while (l <= r) {
                int mid = (l + r) >> 1;
                if (calc(i, mid) == t) l = mid + 1, ans = mid;
                else r = mid - 1;
            }
            change(x, ans, t); x = ans + 1;
        }
    }
    for (int i =  1; i <= n; i++) cout << (pre[i] += pre[i - 1]) << endl;
    return 0;
}

posted @ 2025-05-04 21:14  hnczy  阅读(34)  评论(0)    收藏  举报