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\) 了。
類似的考慮問題即可。