AT4168 [ARC100C] Or Plus Max
Or Plus Max
给你一个长度为 \(2^n\) 的序列 \(a\),每个 \(1\le K\le 2^n-1\),找出最大的 \(a_i+a_j \left( i\ | \ j\le K \ ,i,j \le 2^n\right)\) 并输出。
第一次接触这个比较有趣的思想,主要算法数高维前缀和。
高维前缀和主要用于解决形似 \(\sum_{i\subset S} f(i)\) 的问题,是一种不可多得的人类智慧巧妙算法。
首先一维前缀和不用讲,但二维前缀和其实还有一种不为人知的形式:
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];
看出规律了对吧,逐次按位考虑,每次微调一维。于是利用位运算模拟维数,我们不难得到 \(n\) 维前缀和。
for(int j = 0; j < n; j ++)
for(int i = 0; i < (1 << n); i ++)
if(i >> j & 1) a[i] += a[i ^ (1 << j)];
然而基础算法是一方面,应用又是一方面。
本题我们按照 \(K\) 的不同来考虑,设 \(A_k=\max_{i|j=k}\{a_i+a_j\}\),那么 \(ans_k=\max_{1\leq i\leq k}\{A_i\}\)。
考虑到 \(\max\) 的区间可交性,不妨将限制放宽,与 ST 表的原理类似,设 \(A_k=\max_{i,j\subset k}\{a_i+a_j\}\)。
然后就是比较裸的高维前缀和问题,只是把求和变为取 \(\max\) 了,然后每个数代表两个状态,即最大和次大值。
#include
#include
#include
using namespace std;
const int N = 1 << 19;
const int INF = 1 << 30;
int n;
struct node{int x, y;} a[N];
int read(){
int x = 0, f = 1; char c = getchar();
while(c < '0' || c > '9') f = (c == '-') ? -1 : 1, c = getchar();
while(c >= '0' && c <= '9') x = x * 10 + c - 48, c = getchar();
return x * f;
}
node Merge(node a, node b){
if(a.x < b.x) swap(a, b);
node now = a;
if(a.y < b.x) now.y = b.x;
return now;
}
int main(){
n = read();
for(int i = 0; i < (1 << n); i ++){
a[i].x = read();
a[i].y = - 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].x + a[i].y);
printf("%d\n", ans);
}
return 0;
}