高維字首和

Hugoi發表於2024-10-17

1原來我不會。

子集列舉

列舉一個集合的每個子集的所有子集。

for(int s=0;s<(1<<n);s++){
    for(int s0=s;;s0=s0&(s0-1)){
        cout<<s0<<' ';
    }
}

其中 \(s0\) 即為列舉的每個子集的所有子集。

這是為什麼?

第一層迴圈中,我們列舉了一個子集。

那麼第二層迴圈中,我們就要列舉這個子集的所有子集。

\(s\) 本身開始,從大往小降序列舉。

我們想快速從一個子集跳轉到比它小的第一個子集。

分類討論一下:

若當前的子集最低位為 \(1\),比它小的第一個子集顯然是它 \(-1\)

否則,當前子集最低位為 \(0\),考慮 \(-1\) 以後會發生什麼變化。

發現,會將第一段字尾 \(10000000……\) 變成 \(011111111……\),也就是讓最低位的 \(1\) 變成了 \(0\),最低位之前的 \(0\) 都變成了 \(1\)

此時只需保留原集合中的所有位置即可,所以 \(\&s\) 就行了。

發現我們的列舉不重不漏,那麼複雜度就是所有子集的子集個數的和。

\(\sum_{i=0}^{n}C_{n}^{i}2^i=3^n\)

高維字首和

對於原集合的每一個子集,都有一個初始權值,現在求每一個子集的子集權值和。

for(int i=1;i<=n;i++){
    for(int j=0;j<(1<<n);j++){
        if(s&(1<<(i-1))) f[s]+=f[s^(1<<(i-1))];
    }
}

這該如何理解?

考慮一位一位地加入貢獻。

或者說,每次列舉時都將高位的位置欽定好,只考慮低位位置子集所帶來的貢獻。

這麼說太抽象了,舉個例子吧。

假設當前列舉到了第 \(2\) 位,要貢獻到 \(11111\)

那麼我們就讓當前的 \(11101\) 對它貢獻。

此時,高位的三位是不變的,而 \(11101\) 已經是一個前三位不變,後兩位是子集和的東西了。

\(11101\) 此時已經是 \(11100\)\(11101\) 的和了。

那麼就可以直接讓它貢獻到 \(11111\) 了。

當列舉到第三位時,我們欽定的就是高位的兩位不變了。

對於 \(11111\) ,現在我們讓 \(11011\) 貢獻到它。

此時,\(11011\) 也是欽定了前兩位,後兩位的子集和了。

\(11011\) 此時是原來的 \(11000,11001,11010,11011\) 的和了。

那麼也可以讓它貢獻到 \(11111\) 了。

類似的考慮問題即可。

相關文章