前言
今天中午不知怎么的對這個東西產生了興趣,感覺很神奇,結果花了一個中午多的時間來看QAQ
下面說下自己的理解。
高維前綴和一般解決這類問題:
對於所有的\(i,0\leq i\leq 2^n-1\),求解\(\sum_{j\subset i}a_j\)。
顯然,這類問題可以直接枚舉子集求解,但復雜度為\(O(3^n)\)。如果我們施展高維前綴和的話,復雜度可以到\(O(n\cdot 2^n)\)。
說起來很高級,其實代碼就三行:
for(int j = 0; j < n; j++)
for(int i = 0; i < 1 << n; i++)
if(i >> j & 1) f[i] += f[i ^ (1 << j)];
相信大家一開始學的時候就感覺很神奇,這是什么東西,這跟前綴和有什么關系?
好吧,其實看到后面就知道了。
正文
二維前綴和
一維前綴和就不說了,一般我們求二維前綴和時是直接容斥來求的:
但還有一種求法,就是一維一維來求,也可以得到二維前綴和:
for(int i = 1; i <= n; i++)
for(int j = 1; j <= n; j++)
a[i][j] += a[i - 1][j];
for(int i = 1; i <= n; i++)
for(int j = 1; j <= n; j++)
a[i][j] += a[i][j - 1];
模擬一下就很清晰了。
三維前綴和
同二位前綴和,我們也可以對每一維分別來求:
for(int i = 1; i <= n; i++)
for(int j = 1; j <= n; j++)
for(int k = 1; k <= n; k++)
a[i][j][k] += a[i - 1][j][k];
for(int i = 1; i <= n; i++)
for(int j = 1; j <= n; j++)
for(int k = 1; k <= n; k++)
a[i][j][k] += a[i][j - 1][k];
for(int i = 1; i <= n; i++)
for(int j = 1; j <= n; j++)
for(int k = 1; k <= n; k++)
a[i][j][k] += a[i][j][k - 1];
高維前綴和
接下來就步入正題啦。
求解高維前綴和的核心思想也就是一維一維來處理,可以類比二維前綴和的求法稍微模擬一下。
具體來說代碼中的\(f[i] = f[i] + f[i\ xor\ (1 << j)]\),因為我們是正序枚舉,所以\(i\ xor\ (1 << j)\)在當前層,而\(i\)還在上層,所以我們將兩個合並一下就能求出當前層的前綴和了QAQ。
然后...就完了,好像沒什么好說的。
應用
- 子集
那這跟子集有啥關系?在二進制表示中,發現當\(i\subset j\)時,其實這存在一個偏序關系,對於每一位都是這樣。而我們求出的前綴和就是滿足這個偏序關系的。
回到開始那個問題,初始化\(f[i]=a_i\),直接求高維前綴和,那么最終得到的\(f\)就是答案數組了。
- 超集
理解了子集過后,我們將二進制中的每一個\(1\)當作\(0\)對待,\(0\)當作\(1\)對待求出來的就是超集了~相當於從另一個角出發來求前綴和。
求超集代碼如下:
for(int j = 0; j < n; j++)
for(int i = 0; i < 1 << n; i++)
if(!(i >> j & 1)) f[i] += f[i ^ (1 << j)];
似乎\(FMT\)(快速莫比烏斯變換)就是借助高維前綴和這個東西來實現的。
雖然只有三行代碼,但很神奇QAQ
upd:
這個東西其實和\(sos\ dp\)是一個東西,但感覺用\(dp\)的思想去理解要稍微好一些,就再說一下\(dp\)的想法。
- 子集
我們還是來求子集,定義\(dp_{i,mask}\)為處理了狀態為\(mask\),二進制最后\(i\)位的子集信息時的和。
那么我們枚舉\(i+1\)位時,若當前\(mask\)這一位為\(1\),那么就從\(dp_{i,mask},dp_{i,mask-(1<<i)}\)轉移過來,分別代表有當前這一位時的子集或者沒這一位時的子集,合並一下即可;若當前這位不為\(1\),就從\(dp_{i,mask}\)轉移過來。
最后在代碼中我們一般習慣滾動掉一維。
- 超集
類似地,定義\(dp_{i,mask}\)為當前狀態為\(mask\),處理了后\(i\)位的超集信息時的和。
然后枚舉第\(i+1\)位,若當前這一位為\(0\),就從\(dp_{i,mask},dp_{i,mask+(1<<(i+1))}\)轉移;若當前這位為\(1\),就直接從\(dp_{i,mask}\)轉移過來。
例題
題意:
給出\(2^n\)個數:\(a_0,a_1,\cdots,a_{2^n-1}\)。
之后對於\(1\leq k\leq 2^n-1\),求出:\(a_i+a_j\)的最大值,同時\(i\ or\ j\leq k\)。
思路:
挺奇妙的一個題,需要將問題轉換。
- 發現我們可以對每個\(k\),求出最大的\(a_i+a_j\)並且滿足\(i\ or\ j=k\),最后答案就為一個前綴最大值。
- 但這種形式也不好處理,我們可以將問題進一步轉化為\(i\ or\ j\subset k\)。那么我們就將問題轉化為了子集問題。
- 所以接下來就對於每個\(k\),求出其所有子集的最大值和次大值就行了。
- 直接枚舉子集復雜度顯然不能忍受,其實直接上高位前綴和搞一下就行~
注意一下細節,集合中一開始有一個數。
代碼如下:
#include <bits/stdc++.h>
#define MP make_pair
#define fi first
#define se second
#define sz(x) (int)(x).size()
#define INF 0x3f3f3f3f3f
//#define Local
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
const int N = 20;
int n;
pii a[1 << N];
pii merge(pii A, pii B) {
if(A.fi < B.fi) swap(A, B);
pii ans = A;
if(B.fi > ans.se) ans.se = B.fi;
return ans;
}
void run() {
for(int i = 0; i < 1 << n; i++) {
int x; cin >> x;
a[i] = MP(x, -INF);
}
for(int j = 0; j < n; j++) {
for(int i = 0; i < 1 << n; i++) {
if(i >> j & 1) a[i] = merge(a[i], a[i ^ (1 << j)]);
}
}
int ans = 0;
for(int i = 1; i < 1 << n; i++) {
ans = max(ans, a[i].fi + a[i].se);
cout << ans << '\n';
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
cout << fixed << setprecision(20);
#ifdef Local
freopen("../input.in", "r", stdin);
freopen("../output.out", "w", stdout);
#endif
while(cin >> n) run();
return 0;
}
cf1208F
題意:
給出序列\(a_{1,2\cdots,n},n\leq 10^6\)。
現在要找最大的\(a_i|(a_j\& a_k)\),其中\((i,j,k)\)滿足\(i<j<k\)。
思路:
- 顯然我們可以枚舉\(a_i\),那么問題就轉換為如何快速找\(a_j\& a_k\)。
- 因為最后要使得結果最大,我們二進制從高到底枚舉時肯定是貪心來考慮的:即如果有兩個數他們的與在這一位為\(1\),那么最后的答案中一定有這一位。
- 那么我們逐位考慮,並且考慮是否有兩個在右邊的數他們"與"的結果為當前答案的超集即可,有的話答案直接加上這一位。
- 那么可以用\(sos\ dp\)處理超集的信息,維護在最右端的兩個位置,之后貪心來處理即可。
代碼如下:
/*
* Author: heyuhhh
* Created Time: 2020/2/27 10:51:39
*/
#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
#include <cmath>
#include <set>
#include <map>
#include <queue>
#include <iomanip>
#define MP make_pair
#define fi first
#define se second
#define sz(x) (int)(x).size()
#define all(x) (x).begin(), (x).end()
#define INF 0x3f3f3f3f
#define Local
#ifdef Local
#define dbg(args...) do { cout << #args << " -> "; err(args); } while (0)
void err() { std::cout << '\n'; }
template<typename T, typename...Args>
void err(T a, Args...args) { std::cout << a << ' '; err(args...); }
#else
#define dbg(...)
#endif
void pt() {std::cout << '\n'; }
template<typename T, typename...Args>
void pt(T a, Args...args) {std::cout << a << ' '; pt(args...); }
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
//head
const int N = 2e6 + 5;
int n;
int a[N];
pii dp[N];
void add(int x, int id) {
if(dp[x].fi == -1) {
dp[x].fi = id;
} else if(dp[x].se == -1) {
if(dp[x].fi == id) return;
dp[x].se = id;
if(dp[x].fi < dp[x].se) swap(dp[x].fi, dp[x].se);
} else if(dp[x].fi < id) {
dp[x].se = dp[x].fi;
dp[x].fi = id;
} else if(dp[x].se < id) {
if(dp[x].fi == id) return;
dp[x].se = id;
}
}
void merge(int x1, int x2) {
add(x1, dp[x2].fi);
add(x1, dp[x2].se);
}
void run() {
memset(dp, -1, sizeof(dp));
cin >> n;
for(int i = 1; i <= n; i++) {
cin >> a[i];
add(a[i], i);
}
for(int i = 0; i < 21; i++) {
for(int j = 0; j < N; j++) if(j >> i & 1) {
merge(j ^ (1 << i), j);
}
}
int ans = 0;
for(int i = 1; i <= n - 2; i++) {
int lim = (1 << 21) - 1;
int cur = a[i] ^ lim, res = 0;
for(int j = 20; j >= 0; j--) if(cur >> j & 1) {
if(dp[res ^ (1 << j)].se > i) {
res ^= (1 << j);
}
}
ans = max(ans, res | a[i]);
}
cout << ans << '\n';
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
cout << fixed << setprecision(20);
run();
return 0;
}