【学习笔记】高维前缀和(SOSDP)
高维前缀和(SOSDP)解决这样的问题:
给定 \(f_i\),其中 \(i\in[0,2^n-1]\),求解 \(\sum\limits_{j\subseteq i}f_j\)。
考虑一维前缀和:
复制代码
- 1
- 2
- 3
for(int i=1;i<=n;i++){
sum[i]=sum[i-1]+a[i];
}
二维前缀和:
复制代码
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
sum[i][j]=sum[i][j-1]+a[i][j];
}
}
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
sum[i][j]+=sum[i-1][j];
}
}
三维前缀和:
复制代码
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
for(int k=1;k<=n;k++){
sum[i][j][k]=sum[i][j][k-1]+a[i][j][k];
}
}
}
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
for(int k=1;k<=n;k++){
sum[i][j][k]+=sum[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++){
sum[i][j][k]+=sum[i-1][j][k];
}
}
}
可以发现上面那个问题就是每一维大小都为 \(2\) 的 \(n\) 维前缀和。
因此可以考虑枚举每一维,然后再加上这一维的贡献就好了。
这里分为两种DP:子集和超集。
子集就是指子集。
复制代码
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
for(int i=1;i<=n;i++){
for(int S=0;S<1<<n;S++){
if(S>>(i-1)&1){
continue;
}
f[S|1<<(i-1)]+=f[S];
}
}
自己是超集的子集。
换句话说,上面的问题的判断条件为 \(i\subseteq j\) 时应使用超集。
因为是用大的更新小的,所以 \(S\) 要倒序枚举。
Upd on 2024.12.27:似乎并不用倒序枚举,因为对于每一维,每个 \(S\) 要么只增加要么只被增加,而且只会用一遍。因此不用考虑转移顺序。后面一道题的高维差分也是一样的。
复制代码
- 1
- 2
- 3
- 4
- 5
- 6
- 7
for(int i=1;i<=n;i++){
for(int S=(1<<n)-1;~S;S--){
if(S>>(i-1)&1){
f[S^1<<(i-1)]+=f[S];
}
}
}
CF 1234F
因为字符不重复,因此不用考虑它们的排列顺序,即翻转子串就是将两个不交的子串连到一块。这里不交既指位置不交又指字符集不交。但显然字符集不交则一定位置不交。因此只用考虑对每一个字符集处理最大长度。高维前缀最大值即可。
Code
复制代码
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
using namespace std;
namespace asbt{
namespace cplx{bool begin;}
const int maxn=1e6+5,maxm=(1<<20)+5;
int n,dp[maxm];
char s[maxn];
namespace cplx{
bool end;
il double usdmem(){return (&begin-&end)/1048576.0;}
}
int main(){
scanf(" %s",s+1);
n=strlen(s+1);
for(int i=1;i<=n;i++){
for(int j=i,S=0;j;j--){
if(S>>(s[j]-'a')&1){
break;
}
S|=1<<(s[j]-'a');
dp[S]=i-j+1;
}
}
for(int i=1;i<=20;i++){
for(int S=0;S<1<<20;S++){
if(S>>(i-1)&1){
continue;
}
dp[S|1<<(i-1)]=max(dp[S|1<<(i-1)],dp[S]);
}
}
int ans=0;
for(int S=1;S<=(1<<20)-2;S++){
ans=max(ans,dp[S]+dp[((1<<20)-1)^S]);
}
printf("%d",ans);
return 0;
}
}
int main(){return asbt::main();}
CF 165E
\(a_i\le 4\times 10^6\),也就是说 \(a\) 的全集为 \(2^{22}\)。考虑答案就是 \(a_i\) 的补集的某个子集。SOSDP将 \(a\) 值不断上传即可。注意代码中选择了取 \(\max\),目的是避免被 \(0\) 更新。
Code
复制代码
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
using namespace std;
namespace asbt{
namespace cplx{bool begin;}
const int maxn=1e6+5,maxm=(1<<22)+5;
int n,a[maxn],f[maxm];
namespace cplx{
bool end;
il double usdmem(){return (&begin-&end)/1048576.0;}
}
int main(){
read(n);
for(int i=1;i<=n;i++){
read(a[i]);
f[a[i]]=a[i];
}
for(int i=1;i<=22;i++){
for(int S=0;S<1<<22;S++){
if(S>>(i-1)&1){
continue;
}
int nS=S|1<<(i-1);
f[nS]=max(f[nS],f[S]);
}
}
for(int i=1;i<=n;i++){
int tmp=((1<<22)-1)^a[i];
printf("%d ",f[tmp]?f[tmp]:-1);
}
return 0;
}
}
int main(){return asbt::main();}
AT arc100C
考虑对每个 \(k\),算出 \(i\mid j\subseteq k\) 的答案,然后再对 \(k\) 进行前缀最大值即可。考虑若 \(i\mid j\subseteq k\),则一定满足 \(i\subseteq k\),\(j\subseteq k\)。因此高维前缀最大值、次大值即可。
Code
复制代码
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
using namespace std;
namespace asbt{
namespace cplx{bool begin;}
const int maxn=(1<<18)+5;
int n,f[maxn],g[maxn],hp[7],ans[maxn];
namespace cplx{
bool end;
il double usdmem(){return (&begin-&end)/1048576.0;}
}
int main(){
read(n);
for(int i=0;i<1<<n;i++){
read(f[i]);
}
for(int i=1;i<=n;i++){
for(int S=0;S<1<<n;S++){
if(S>>(i-1)&1){
continue;
}
int nS=S|1<<(i-1);
hp[1]=f[S],hp[2]=g[S];
hp[3]=f[nS],hp[4]=g[nS];
sort(hp+1,hp+5);
f[nS]=hp[4],g[nS]=hp[3];
}
}
for(int i=1;i<1<<n;i++){
ans[i]=max(ans[i-1],f[i]+g[i]);
printf("%d\n",ans[i]);
}
return 0;
}
}
int main(){return asbt::main();}
CF 1208F
首先,\(a_i\mid(a_j\& a_k)=((\complement_Ua_i)\&a_j\&a_k)+a_i\)。
二进制数最大,我们考虑按位贪心。枚举到 \(i\) 时,若要这一位为 \(1\),则必须满足至少有两个 \(a\) 值的这一位为 \(1\),且下标必须大于 \(i\)。SOSDP维护超集的最大与次大位置即可。
\(i\) 的枚举只能到 \(n-2\),因为 \(i=n-1\) 或 \(n\) 时 \(((\complement_Ua_i)\&a_j\&a_k)\) 这一部分会被算成 \(0\),答案会被 \(a_i\) 直接更新。
Code
复制代码
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
using namespace std;
namespace asbt{
namespace cplx{bool begin;}
const int maxn=1e6+5;
const int maxm=(1<<21)+5,uS=(1<<21)-1;
int n,a[maxn],f[maxm],g[maxm],hp[10];
namespace cplx{
bool end;
il double usdmem(){return (&begin-&end)/1048576.0;}
}
int main(){
read(n);
for(int i=1;i<=n;i++){
read(a[i]);
hp[1]=f[a[i]];
hp[2]=g[a[i]];
hp[3]=i;
sort(hp+1,hp+4);
f[a[i]]=hp[3];
g[a[i]]=hp[2];
}
for(int i=1;i<=21;i++){
for(int S=uS;~S;S--){
if(S>>(i-1)&1){
int nS=S^1<<(i-1);
hp[1]=f[S],hp[2]=g[S];
hp[3]=f[nS],hp[4]=g[nS];
sort(hp+1,hp+5);
f[nS]=hp[4],g[nS]=hp[3];
}
}
}
// for(int i=0;i<=15;i++){
// cout<<f[i]<<" "<<g[i]<<"\n";
// }
int ans=0;
for(int i=1;i<=n-2;i++){
int nS=uS^a[i],res=0;
// cout<<i<<"\n";
for(int j=21;~j;j--){
// cout<<" "<<j<<"\n";
if(nS>>j&1){
res|=1<<j;
if(g[res]<=i){
res^=1<<j;
}
}
}
ans=max(ans,a[i]+res);
}
printf("%d",ans);
return 0;
}
}
int main(){return asbt::main();}
/*
3
1 5 15
*/
CF 383E
考虑单词不合法,即每一个字母都不是元音。
对每个单词状压,然后高维前缀和计算每一个字符集包含的单词数量,最后枚举元音的补集统计答案。
Code
复制代码
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
using namespace std;
namespace asbt{
namespace cplx{bool begin;}
const int maxn=(1<<24)+5;
int n,f[maxn];
char s[10];
namespace cplx{
bool end;
il double usdmem(){return (&begin-&end)/1048576.0;}
}
int main(){
read(n);
for(int i=1,S;i<=n;i++){
scanf(" %s",s+1);
S=0;
for(int j=1;j<=3;j++){
S|=1<<(s[j]-'a');
}
f[S]++;
}
for(int i=1;i<=24;i++){
for(int S=0,nS;S<1<<24;S++){
if(S>>(i-1)&1){
continue;
}
nS=S|1<<(i-1);
f[nS]+=f[S];
}
}
int ans=0;
for(int i=0;i<1<<24;i++){
ans^=(n-f[i])*(n-f[i]);
}
printf("%d",ans);
return 0;
}
}
int main(){return asbt::main();}
CodeChef COVERING
令 \(f_S=\sum\limits_{i\mid j\mid k=S}a_ib_jc_k\),则不难发现答案即为 \(\sum f_S\times 2^{|S|}\)。
考虑如何求解 \(f\),不难想到令 \(A_S\),\(B_S\),\(C_S\) 为 \(\sum\limits_{i\subseteq S}a_i\),\(\sum\limits_{i\subseteq S}b_i\),\(\sum\limits_{i\subseteq S}c_i\),则有:
移项得:
高维差分即可。
Code
复制代码
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
using namespace std;
namespace asbt{
namespace cplx{bool begin;}
const int maxn=(1<<20)+5,mod=1e9+7;
int n,f[maxn];
int a[maxn],b[maxn],c[maxn];
namespace cplx{
bool end;
il double usdmem(){return (&begin-&end)/1048576.0;}
}
int main(){
read(n);
for(int i=0;i<1<<n;i++){
read(a[i]);
}
for(int i=0;i<1<<n;i++){
read(b[i]);
}
for(int i=0;i<1<<n;i++){
read(c[i]);
}
for(int i=1;i<=n;i++){
for(int S=0,nS;S<1<<n;S++){
if(S>>(i-1)&1){
continue;
}
nS=S|1<<(i-1);
(a[nS]+=a[S])%=mod;
(b[nS]+=b[S])%=mod;
(c[nS]+=c[S])%=mod;
}
}
for(int i=0;i<1<<n;i++){
f[i]=a[i]*1ll*b[i]%mod*c[i]%mod;
}
for(int i=1;i<=n;i++){
for(int S=0;S<1<<n;S++){
if(S>>(i-1)&1){
continue;
}
(f[S|1<<(i-1)]+=mod-f[S])%=mod;
}
}
int ans=0;
for(int i=0;i<1<<n;i++){
(ans+=f[i]*(1ll<<popcnt(i))%mod)%=mod;
}
printf("%d",ans);
return 0;
}
}
int main(){return asbt::main();}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步