P8165 [eJOI 2021] AddK 题解

· · 题解

极小(K\le10)的区间修改以及区间查询,考虑树状数组。

可以说是分为三个部分,左边是斜率一致的上坡,中间平坦,右边是斜率一致的下坡。那么答案可以写为

\text{ans}= \sum_{i=l}^{l+m-1}(i-l+1)A_i +\sum_{i=l+m}^{r-m}mA_i +\sum_{i=r-m+1}^{r}(r-i+1)A_i

中间的平坦部分使用树状数组 S 维护 \sum A_i 即可;两边的坡由于斜率一致且为 45\degree,可以考虑再使用一个树状数组 T 维护 \sum i\cdot A_i 计算。记

S(x,y) = \sum_{i=x}^{y}A_i,\ T(x,y)=\sum_{i=x}^{y}i\cdot A_i

上式等价于

\begin{aligned} \text{ans} = \ &\Big(T(l,l+m-1)-(l+m-1)\cdot S(l,l+m-1)\Big)\\ &+(m\cdot S(l,r))\\ &+\Big((r-m+1)\cdot S(r-m+1,r)-T(r-m+1,r)\Big) \end{aligned}

时间复杂度为 O(QK\log N)

#include<bits/stdc++.h>
using namespace std;
#define int long long

const int MAX = 1e5 + 5;
int N, K, A[MAX], S[MAX], T[MAX];
int lowbit(int x) { return x & (-x); }
void add(int *t, int u, int x) { for(int i = u; i <= N; i += lowbit(i)) t[i] += x; }
int sum(int *t, int u) { int tol = 0; for(int i = u; i; i -= lowbit(i)) tol += t[i]; return tol; }
int sum(int *t, int l, int r) { return sum(t, r) - sum(t, l - 1); }

signed main()
{
    cin >> N >> K;
    for(int i = 1; i <= N; i++) {
        cin >> A[i];
        add(S, i, A[i]);
        add(T, i, i * A[i]);
    }
    int Q; cin >> Q;
    while(Q--) {
        int opt; cin >> opt;
        if(opt == 1) {
            vector<int> pos(K), old(K);
            for(int i = 0; i < K; i++) cin >> pos[i], old[i] = A[pos[i]];
            for(int i = 0; i < K; i++) {
                int delta = old[(i + 1) % K] - old[i];
                add(S, pos[i], delta);
                add(T, pos[i], delta * pos[i]);
                A[pos[i]] = old[(i + 1) % K];
            }
        } else {
            int l, r, m; cin >> l >> r >> m;
            int ans = (m * sum(S, l, r))
                    + (sum(T, l, l + m - 1) - (l + m - 1) * sum(S, l, l + m - 1))
                    + ((r - m + 1) * sum(S, r - m + 1, r) - sum(T, r - m + 1, r));
            cout << ans << endl;
        }
    }
    return 0;
}