SOS dp

前置知识

简单dp,循环,二进制。

应用范围

高维前缀和,子集和,超集和,FWT。

思路

我们以高维前缀和(注意每一位只有0/1)为例来思考。

高维前缀和

先给出一维前缀和的形式的求法。

for(int i=1;i<=n;i++)
	sum[i]+=sum[i-1];

二维前缀和(非容斥写法,但是显然正确)的求法

for(int i=1;i<=n;i++)
   	for(int j=1;j<=n;j++)
        sum[i][j]+=sum[i][j-1];//可以理解为先处理出列的前缀和
for(int i=1;i<=n;i++)
    for(int j=1;j<=n;j++)
        sum[i][j]+=sum[i-1][j];//再处理行的前缀和。

我们可以受到启发,一维一维的处理答案。首先,因为每一维只有 \((0,1)\) 所以我们可以状压。我们\(f_{i,s}\) 表示处理了前 \(i\) 维状态为 \(s\) 的答案。我们考虑转移,如果这一维是 \(1\) ,有 \(f_{i,s}=f_{i-1,s}+f_{i,s\oplus(1<<i)}\) 。什么意思?我们现在和二维的情况是一样(前面 \(i-1\) 处理完了合起来看,当前处理的这一维),加 \(f_{i,s\oplus(1<<i)}\) 表达就是第 \(i\) 维的前缀和(\(1\) 的情况加上 \(0\)),\(f_{i-1,s}\) 表示就前 \(i\) 维的前缀和(这一维加前 \(i-1\) 维)。如果这一维是 \(0\) 的话,就直接从 \(f_{i-1,s}\) 转移即可。滚掉第一维后,可以写出以下的代码。

for(int i=0;i<n;i++)
    for(int j=0;j<(1<<n);j++)
        if((j>>i)&1)f[j]=(f[j]+f[i^(1<<i)])%mod;

我再说一点我当时觉得难以理解的地方是 dp 状态 \(s\) ,其实它就二维里的 \([i][j]\) 只是状压成一个数方便存储。同时在处理前 \(i\) 维时,只用了 \(s\) 的最低的 \(i\) 维。

子集和

我们考虑知道任意一个集合 \(S\) 的答案,要求集合 \(S\)\(S\) 所有子集的答案和。 我们可以将每一个元素选或不选 (\(1/0\) 来表示)状压出的数表示 \(S\) ,我们发现 \(S\) 的子集和就是高维前缀和,因为某一维 \(S\)\(1\) ,加上 \(0\) 的情况就表示 \(S\) 中有,但是不选,显然 \(S\) 的所有子集都会被算一遍。

代码与高维前缀和一模一样,就不放了。

超集和

\(S\) 的超集就所有集合 \(T\) 满足 \(S\)\(T\) 的子集。

显然现在不是 \(S\)\(1\) 可以选 \(0\) 的情况,而是\(S\)\(0\) ,可以选 \(1\) 的情况。将高维前缀和转移变成当前位为 \(0\) 时 加上 \(1\) 的答案。(应该叫它高维后缀和)

代码如下 。

for(int i=0;i<n;i++)
    for(int j=0;j<(1<<n);j++)//一开始对枚举顺序有疑问,按照定义应该从小到大枚举,因为这里只有0和1,且当前1直接继承,所以f[i-1][j^(1<<i)]与f[i][j^(1<<i)]相等
        if(!((j>>i)&1))f[j]=(f[j]+f[j^(1<<i)])%mod;

高维前缀差分

有前缀和与差分肯定是相生相伴的。所以我们考虑一个问题是我们已知集合 \(S\)\(S\) 的所有子集的答案和,如何求 \(S\) 的答案。我们可以仿照前缀和定义dp,设 \(f_{i,s}\) 表示分离了前 \(i\) 维状态为 \(S\) 的答案(分离指的是已经不包含前 \(i\)\(S\)\(1\) 的时候取 \(0\) 的答案)。dp转移与前缀和相比只变了符号。

代码如下。

for(int i=0;i<n;i++)
    for(int j=0;j<(1<<n);j++)
        if((j>>i)&1)f[i]=(f[i]-f[i^(1<<k)])%mod;

例题

等我写完 FMT,再回来补。

posted @ 2025-07-14 22:00  exCat  阅读(27)  评论(2)    收藏  举报