1. 程式人生 > 其它 >高維字首和(sosdp) & AT4168 [ARC100C] Or Plus Max

高維字首和(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;
}