子集反演 & 高维前缀和 & sos dp 学习笔记

子集反演 & 高维前缀和 & sos dp 学习笔记

子集反演

\(g(S)\) 表示集合 \(S\) 的答案,\(f(S)\)\(S\) 的子集的答案和。

根据定义:

\[f(S)=\sum_{T\in S} g(T) \]

子集反演就是:

\[g(S)=\sum _{T\in S}(-1)^{|S|-|T|}f(T) \]

本质上就是容斥原理,可感性理解,证明略(给你你也记不住)。

于是便可以通过求 \(f\) 得到 \(g\)

高维前缀和

从低维向高维考虑,先来看看二维前缀和:

for(int i=1;i<=n;i++)
    for(int j=1;j<=m;j++)
        s[i][j]=s[i][j]+s[i-1][j]+s[i][j-1]-s[i-1][j-1];

这是运用了容斥原理,s[i-1][j]s[i][j-1] 多加了一个 s[i-1][j-1],于是把它减掉。

但是我们可以运用更加普遍性的做法,对每一维分开求前缀和。

for(int i=1;i<=n;i++)
    for(int j=1;j<=m;j++)
        s[i][j]=s[i-1][j]+s[i][j];
for(int i=1;i<=n;i++)
    for(int j=1;j<=m;j++)
        s[i][j]=s[i][j-1]+s[i][j];

我们先把行加起来,之后把加完的行按列加起来。

以第二种方法做三维前缀和也是类似的:

for(int i=1;i<=n;i++)
    for(int j=1;j<=m;j++)
        for(int k=1;k<=p;k++)
            s[i][j][k]=s[i][j][k]+s[i-1][j][k];
for(int i=1;i<=n;i++)
    for(int j=1;j<=m;j++)
        for(int k=1;k<=p;k++)
            s[i][j][k]=s[i][j][k]+s[i][j-1][k];
for(int i=1;i<=n;i++)
    for(int j=1;j<=m;j++)
        for(int k=1;k<=p;k++)
            s[i][j][k]=s[i][j][k]+s[i][j][k-1];

更高维的前缀和也可以这样求。

于是我们得到了一个 \(O(n^tt)\)\(t\) 维前缀和做法,先枚举维度,对每一维度分别求和。

例题

给定长为 \(n\) 的数组 \(a\),设 \(s_i=\sum_{j=1}^ia_j\),满足 \(s_n\le 10^{12}\)

\(Q\) 个询问,每次给定 \(q\),满足 \(q|s_n\),求 \(\sum_{i=1}^n[q|s_i]\)

\(n\le10^5,Q\le 10^5,1\le a_i \le 10^{12}\)


可知 \(q|s_n,q|s_i\),所以先把每个 \(s_i\)\(s_n\) 取 gcd。

则此时 \(s_i\) 都为 \(s_n\) 的因数,考虑对 \(s_n\) 质因数分解,则

\[s_n=\prod_{i=1}^kp_i^{c_i} \]

然后我们就能快速求出 \(s_j\) 的质因数分解:

\[s_j=\prod_{i=1}^kp_i^{b_i} \]

则有 \(b_i\le c_i\)

我们可以预处理每个 \(q\) 的答案。

我们把 \(s_n\) 的因数映射到一个较小的范围内。

\(f_i\)\(i\) 是多少个 \(s_j\) 的因数,则初始 \(f_{s_j}=1\),我们求一遍高维后缀和,就能知道每一个因数的答案了。

如何映射?

把一个分解方式压成整数,可以把它看成每一位都是不同进制的数,第 \(i\) 位的进制即 \(c_i+1\)

则第 \(i\) 为的基数为 \(w_i=\prod_{j=1}^{i-1}(c_j+1)\),此时一个因数的映射就是 \(p_i=\sum _{j=1}^k c_jw_j\)

转移数组

和上面高维前缀和一样,先枚举维度,固定其他维度,只变化这个维度做转移,大致代码如下:

for(int i=1;i<=k;i++)//维度
    for(int j=S;j>=0;j--)//S为sn映射后的值
        f[j]=f[j]+f[j+w[i]];   

sos dp

对于每个 \(0\le i<2^n\),求 \(f_i=\sum _{j\in i} a_j\)

朴素的做法是直接枚举子集,暴力地是 \(O(4^n)\),初始时 \(f_i=a_i\)

for(int i=0;i<1<<n;i++)
    for(int j=0;j<i;j++)
        if((i|j)==i) f[i]+=f[j];

而众所周知,枚举子集可以做到 \(O(3^n)\),于是

for(int i=1;i<1<<n;i++){
    f[i]+=f[0];
    for(int j=i;j;j=(j-1)&i)
        f[i]+=f[j];
} 

然而,引入高维前缀和的思想,每一位都可以看做一个维度,先枚举维度,对每个维度分别求和,可以做到 \(O(n2^n)\)

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

我们还可以对超集求和(如果 \(S\)\(T\) 的子集,那么 \(T\)\(S\) 的超集),只要把 0 变成 1 做即可,注意由于小的由大的转移过来,因此需要从大往小枚举:

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

另外,对子集或超集求最值也是类似的。

例题 P6442 [COCI2011-2012#6] KOŠARE

题目大意:给定全集的 \(n\) 个子集,求使得所选集合的并集为全集的选择方案数,全集大小为 \(m\)

  • \(n\le 10^6,m\le 20\)

由于 \(m\le 20\),我们把一个子集压成二进制。

\(a_S\) 为集合为 \(S\) 的个数。

我们设 \(b_S=\sum _{T\in S} a_T\),即集合为 \(S\) 的子集的个数。

那么设 \(f_S=2^{b_S} -1\),就是并集为 \(S\) 的子集的方案数。

\(b_S\) 可以用 sos dp 求。

知道 \(f_S\) 后,根据子集反演就可以得到答案为

\[\sum (-1)^{m-|S|}f_S \]

二进制下的 \(|S|\) 就是 \(popcnt\),即二进制中 \(1\) 的个数。

例题 arc184_b

题目大意:选择 \(x\),可以覆盖 \(x,2x,3x\),求最少选多少个数可以覆盖完所有 \([1,n]\) 的整数,\(1\le n\le 10^9\)

可以把所有数表示成 \(2^x3^yz\),其中 \(2\nmid z\and 3\nmid z\)
然后可以把 \(z\) 相同的数放在一起,每个数放在第 \(x\) 行、第 \(y\) 列的位置,然后组成一个阶梯状的表。
那么题目相当于选择一个数,把它自己、它下面、和它右边的数覆盖。

由于 \(\log_3 n\le18\) 于是考虑状压 DP。

首先第一行要满足不能出现相邻的 0。
然后考虑后面的行。我们把上一行在这一行范围内的状态取反,相当于我们这一行的 1 要覆盖这些取反后的 1。

如果我们这一行选了 \(S\),那么这一行能覆盖 S|(S<<1)。能覆盖的状态就是 S|(S<<1) 的子集,做一遍 sos dp 即可。

但是每个数都要遍历一遍,复杂度是不小于 \(O(n)\) 的。

考虑优化,通过打表可以发现,对于 \(\Big \lfloor \dfrac n z\Big \rfloor\) 相同的 \(z\),它们的表长的一样。这些 \(z\) 批量处理即可。

时间复杂度不好表示,总之效率大大优化,可以在 \(2s\) 内通过。

类似的题 P3226 HNOI2012 集合选数TFSETS - Triple-Free Sets

本题代码:

int n;
int ans=0;
int l[31],d;
int f[31][1<<20],g[1<<20];
signed main(){
	read(n);
	for(int L=1,R;L<=n;L=R+1){
		R=n/(n/L);
		int cnt=0;
		fo(i,L,R)if(i%2!=0&&i%3!=0)++cnt;
		fo(i,L,R){
			if(i%2!=0&&i%3!=0){
				int x=i;
				d=0;
				while(x<=n){
					l[++d]=0;
					unsigned y=x;
					while(y<=n)y*=3,++l[d];
					x<<=1;
				}
				fu(j,0,1<<l[1]){
					if(((j|(j<<1))&((1<<l[1])-1))==(1<<l[1])-1)f[1][j]=popcnt(j);
					else f[1][j]=1e9;
				}
				fo(j,2,d){
					fu(k,0,1<<l[j])g[k]=1e9;
					fu(k,0,1<<l[j-1]){
						gmin(g[((1<<l[j])-1)^(k&((1<<l[j])-1))],f[j-1][k]);
					}
					fu(p,0,l[j])fu(k,0,1<<l[j]){
						if(k>>p&1)g[k]=min(g[k],g[k^(1<<p)]);
					}
					fu(k,0,1<<l[j]){
						f[j][k]=g[(k|(k<<1))&((1<<l[j])-1)]+popcnt(k);
					}
				}
				int sum=1e9;
				fu(j,0,1<<l[d]){
					sum=min(sum,f[d][j]);
				}
				ans+=cnt*sum;
				break;
			}
		}
	}
	write(ans);
	return 0;
}
posted @ 2024-09-25 22:24  dengchengyu  阅读(22)  评论(0编辑  收藏  举报