高维前缀和(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;
}
posted @ 2021-07-20 17:52  尹昱钦  阅读(186)  评论(0编辑  收藏  举报