高维前缀和(sosdp) & AT4168 [ARC100C] Or Plus Max
洛谷传送门
高维前缀和
一维二维前缀和
首先多维前缀和肯定可以像二维一样进行容斥求出,但是很显然复杂度爆炸。
所以我们使用另一种求法。
二维前缀和:
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
a[i][j]+=a[i-1][j];
}
}
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
a[i][j]+=a[i][j-1];
}
}
可以理解为先对于每一列求关于行的前缀和,再在每一行加起来。
三维前缀和:
for(int i=1;i<=a;i++){
for(int j=1;j<=b;j++){
for(int k=1;k<=c;k++){
a[i][j][k]+=a[i-1][j][k];
}
}
}
for(int i=1;i<=a;i++){
for(int j=1;j<=b;j++){
for(int k=1;k<=c;k++){
a[i][j][k]+=a[i][j-1][k];
}
}
}
for(int i=1;i<=a;i++){
for(int j=1;j<=b;j++){
for(int k=1;k<=c;k++){
a[i][j][k]+=a[i][j][k-1];
}
}
}
这样n维的前缀和时间复杂度就降到了 \(O(na^n)\)
应用--子集
例如
对于所有的 \(i(0≤i≤2n−1)\),求解 \(\sum_{j⊂i}a_j\)。
令 dp[i][j] 表示考虑数 j 二进制的后 i 位的子集和。
于是就有了代码:
for(int i=1;i<=n;i++){
for(int j=0;j<(1<<n);j++){
if(j&(1<<(i-1))) dp[i][j]+=dp[i-1][j^(1<<(i-1))];
else dp[i][j]=dp[i-1][j];
}
}
但是一般用滚动数组优化一下,于是就有:
for(int i=0,i<n;i++){
for(int j=0;j<(1<<n);j++){
if(j&(1<<i)) dp[j]+=dp[j^(1<<i)];
}
}
解题思路
可以用求子集的方法来解此题。
用d[i][0/1]表示i的子集中的最大值/次大值。
注意最后因为题目要求是<=k,所以要取max。
AC代码
#include<cstdio>
#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<ctime>
using namespace std;
const int maxn=3e5;
int nn,n,a[maxn],d[maxn][2],ans;
void add(int a,int b){
if(d[a][0]<d[b][0]){
d[a][1]=max(d[a][0],d[b][1]);
d[a][0]=d[b][0];
}
else if(d[a][1]<d[b][0]) d[a][1]=d[b][0];
}
int main(){
ios::sync_with_stdio(false);
cin>>nn;
n=1<<nn;
for(int i=0;i<n;i++) cin>>d[i][0];
for(int i=0;i<nn;i++){
for(int j=0;j<n;j++){
if(j&(1<<i)) add(j,j^(1<<i));
}
}
for(int i=1;i<n;i++){
ans=max(ans,d[i][0]+d[i][1]);
cout<<ans<<endl;
}
return 0;
}