從CF1879D學習一類區間貢獻題思路

加固文明幻景發表於2024-06-15

https://codeforces.com/contest/1879/problem/D

關鍵在於互換兩個 \(\sum\)​ 的順序

一般像這樣計算所有子區間的式子,如果要最佳化成接近線性,有一種可行思路是把注意力放在右端點,透過不斷移動右端點,在移動的時候利用前面的統計結果算出移動右端點後的結果,從而得出所有子區間的答案。然後還有一個顯然有用的套路是字首和,這裡記 $s_i=a_1 \oplus a_2\oplus ···\oplus a_{n-1}\oplus a_n $ ,\(s_{i,j}\)\(s_i\) 二進位制的第 \(j\) 位,我們會發現 \(\sum_{l=1}^{n}\sum_{r=l}^{n}f(l,r)=\sum_{i=0}^{32}\sum_{l=1}^{n}\sum_{r=l}^{n}s_{l,i} \oplus s_{r,i}\times 2^i\)

由於上面的式子可以最佳化成 \(O(n\operatorname{log}V)\),我們受到啟發推出下面的式子

\(\sum_{l=1}^{n}\sum_{r=l}^{n}f(l,r)\times(r-l+1)\)

\(=\sum_{r=1}^{n}\sum_{l=1}^{r}f(l,r)\times(r-l+1)(調換∑位置)\)

\(=\sum_{r=1}^{n}\sum_{l=1}^{r}\sum_{i=0}^{32} s_{l,i} \oplus s_{r,i}\times 2^i\times(r-l+1)\)

\(=\sum_{r=1}^{n}\sum_{i=0}^{32} \sum_{l=1}^{r}[ s_{r,i}\oplus s_{l,i} =1]\times 2^i\times(r-l+1)\)

\(=\sum_{r=1}^{n}\sum_{i=0}^{32} 2^i\times\sum_{l=1}^{r}([ s_{r,i}\oplus s_{l,i} =1]\times r-[ s_{r,i}\oplus s_{l,i} =1] \times (l-1))\)

\(r\)​ 視作常量再觀察上面的式子,你就會發現最後一維可以被最佳化掉,因為 $\displaystyle \sum_{l=1}^{r}[ s_{r,i}\oplus s_{l,i} =1]\times r $​ 和 \(\displaystyle \sum_{l=1}^{r}[ s_{r,i}\oplus s_{l,i} =1]\times (l-1)\)​ 都可以預處理。

void solve()
{
// #define tests
    int n; std::cin >> n;
    std::vector<int> a(n); for (auto& x : a) {std::cin >> x;}
    PrefixXor<i64> pre_xor(a);
    
    std::vector cnt(2, std::vector<Z>(31));
    auto sum(cnt);//維護該數之前該位上為0/1的個數
    cnt[0].assign(31, 1);//一開始0的個數賦成1
    Z ans = 0;
    for (int r = 1; r <= n; r++) {//固定r,更新答案
        for (int j = 30; j >= 0; j--) {
            int x = (pre_xor(r) >> j & 1);
            ans += (cnt[x ^ 1][j] * r - sum[x ^ 1][j]) * (1 << j);//找當前位相反的數碼的數量,只用這樣才能產生貢獻,即異或和為 1
            sum[x][j] += r; cnt[x][j] += 1;
        }
    }
    std::cout << ans << '\n';
}

相關文章