高維字首和(sosdp) & AT4168 [ARC100C] Or Plus Max
阿新 • • 發佈:2021-07-20
洛谷傳送門
高維字首和
一維二維字首和
首先多維字首和肯定可以像二維一樣進行容斥求出,但是很顯然複雜度爆炸。
所以我們使用另一種求法。
二維字首和:
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; }