SOSDP学习笔记

高维前缀和DP

定义:

问题: 对于所有的 \(i\) 满足 \(0≤i≤2^n−1\),求解\(\sum_{j\subset i} a_j\)

将每一个 \(i\) 看作一个集合的编号,$ a_i $ 看作集合的权值,本题即求带权子集权值和问题。

暴力做法:遍历每一个 \(i\) 并枚举子集求和,复杂度为 \(O(n^3)\)

高维前缀和DP:利用容斥原理,可以以 \(O(n 2^n)\) 的复杂度处理出 \(dp\) 数组 ,其中 $ dp_i$ 表示编号为 \(i\)​​ 的集合的子集的权值和。

故高维前缀和DP就是用于求解带权子集权值和问题的算法。

引入:

此问题看起来和高维前缀和关系不大,实际上密切相关,首先重新梳理一下前缀和的知识:

因为各个维度上分别求前缀和是互不影响的(容斥原理),所以有以下求高维前缀和的方法,以三维举例,分别对每一维进行求前缀和,最后即可得到总的前缀和

for(int i = 1; i <= x; i++)
    for(int j = 1; j <= y; j++)
        for(int k = 1; k <= z; k++) 
            a[i][j][k] += a[i - 1][j][k];
for(int i = 1; i <= x; i++)
    for(int j = 1; j <= y; j++)
        for(int k = 1; k <= z; k++)
            a[i][j][k] += a[i][j - 1][k];
for(int i = 1; i <= x; i++)
    for(int j = 1; j <= y; j++)
        for(int k = 1; k <= z; k++)
            a[i][j][k] += a[i][j][k - 1];

类似的,对于更高的维度我们可以得到伪代码,设高维空间 \(U\)\(D\) 维,高维空间的大小为 \(|U|\)​​ , 伪代码如下:

for i = 1 to D {//枚举当前求前缀和的维度
	for j in |U| {//枚举 D 维空间中的每一个点
		if j 在 i 维度上的值不为0 {
        	令 j` = j 在 i 维度上 - 1;
        	则有 sum[j] = sum[j] + sum[j`];//更新 sum[j]
    	}
    }
}

如果有一高维空间,维度为 \(n\) 维, 每一维的大小均为 \(2\) ,则高维空间的大小为 \(2^n\)​ ,我们对这个高维空间求高维前缀和,这个高维空间内的每一个点都可以被看做一个二进制数,而对于这个点求出的高维前缀和就是这个点所对应的二进制数的子集和

实现:

用状压DP的思想,将 \(2^n\) 大小的 \(n\) 维空间用二进制数 \(S\) 表示,第 \(i\) 个二进制位上的值即第 \(i\) 维值的大小,接下来只需要在状压的数组上跑刚刚的伪代码:

\(dp\) 数组的定义: $ dp_i$ 表示编号为 \(i\) 的集合的子集的权值和高维空间中各个维度上的值的二进制表示为 \(i\) 的点对应的高维前缀和

for(int i = 0; i < n; i++) //枚举当前求前缀和的维度
    for(int j = 0; j < 1 << n; j++)//枚举 n 维空间中的每一个点
        if(j & (i << 1)) //若当前点在 i 维度上的值不为 0
            dp[j] += dp[j ^ (1 << i)];//更新 dp[j] , 其中 j ^ (1 << 1)等价于j - (1 << i),前者运算速度更快

算法的时间复杂度为 \(O(n 2^n)\) ,空间复杂度为 \(O(2^n)\)​ 。

例题:

posted @ 2024-03-04 06:38  格里恩佐夫  阅读(150)  评论(1)    收藏  举报