高维前缀和(SoSDP)详解及例题(HDU5765)

目录

高维前缀和

问题:求n个位的子集或超集上的函数和。例如,(n=4),集合(mask=1010)(二进制位串表示,下同),(mask)的子集和是(f(0000) + f(0010) + f(1000) + f(1010)),超集和是(f(1010) + f(1011) + f(1110) + f(1111))
解读一下问题先:

  1. 这个问题可看成是一维部分和、二维部分和在(n)维布尔空间上的推广。
  2. 许多应用需要求子集或超集的个数,这相当于(f)函数是常函数(f(x)=1)的情况。

可用(n2^n)种状态求(n)个位的子集或超集上的函数和,称为SoSDP(Sum of Subset DP)。做法是从最低位开始枚举每一位,再枚举每个数(集合),如果这个数在这一位存在真子集或真超集,就加上这个真子集或真超集的函数和。
以求子集和为例,设(dp_{m,i}(0le m le 2n-1,0le ile n-1))表示(m)集合上仅第(0-i)位允许不同的所有子集的函数和。若(i=2,m=1101)(m)的第2位是1,说明这一位存在真子集(1001),故(dp_{1101,2} = dp_{1101,1}+dp_{1001,1}),表示是前一(i)状态的两个(dp)之和,一个(dp)是第(i)位为(1)的子集函数和,另一个(dp)是第(i)位为(0)的子集函数和。相较之下,若(m=1001),则(dp_{1001,2} = dp_{1001,1}),表示与前一(i)状态一样。这就是高维前缀和从状态(i-1)转到状态(i)的处理办法。问题最终所求是(dp_{mask,n-1})

因为(i)状态的计算仅依赖于(i-1)状态,状态空间可以使用滚动数组并就地更新,压缩到(i)方向只需一行。代码如下:

for (int m = 0; m < (1 << n); m++)
	dp[m] = f(m);
for (int i = 0; i < n; i++) {
	for (int m = 0; m < (1 << n); m++)
		if (m & (1 << i))
			dp[m] += dp[m & ~(1 << i)];

HDU5765

题意: 求无向图所有最小割中每条边的出现次数。
题解: 每个最小割将图分为两个连通块。每个连通块(连通的顶点子集生成子图),若其剩余图(顶点补集的生成子图)也是一个连通块,则两个连通块间的边组成一个最小割,所以枚举所有的连通块能统计出所求次数。需要完成两项任务:

  1. 任意一个顶点子集是否生成连通块?
  2. 已知最小割,如何统计连接两个连通块间的边?

第1项任务用并查集判断每个子集的连通性,计算复杂性是(O(m2^n)= O(n^22^n)),会超时。状压DP可以提高效率至(O(n2^n)),用二进制位串表示顶点子集。若顶点(i)不在顶点子集(S),且与(S)中某个点有边,则(Scup{i})也是连通块。据此进行状态转移。
第2项任务如果枚举最小割的每条边,计算复杂性也会高达(O(n^22^n))。逆向思维一下,一条边不在最小割的次数如果知道,最小割的总数减去这个次数就是答案。那么边((u,v))不在最小割有多少次?这个问题等价于在最小割形成的连通块集合中统计((u,v))的超集个数,高维前缀和的SoSDP能(O(n2^n))时间内解决。具体用(dp_{s,b})表示限第(0-b)点可变的(s)的超集个数,对所有能由最小割形成的连通块,置初值(dp_{s,-1}=1)。然后使用状态转移方程:

[dp_{s,b}=egin{cases} dp_{s,b-1}qquad bin s\ dp_{s,b-1}+dp_{scup {b},b-1}qquad b otin s end{cases} ]

滚动数组优化后代码:

for (int b = 0; b < n; b++)
		for (int s = (1<<n) - 1; s >= 0; s--)
			if (!(s & (1<<b)))
				dp[s] += dp[s | (1<<b)];

over.
Accept Code:

#include <bits/stdc++.h>
using namespace std;

int n,m;
const int maxn=1<<20;
int edg[20],u[maxn],v[maxn];
bool c[maxn];
int dp[maxn];
void init(){
    memset(c,false,sizeof c);
    for(int i=0;i<n;i++)c[1<<i]=true;
    for(int s=0;s<(1<<n);s++){
        if(c[s]){
            for(int i=0;i<n;i++){
                if(!(s&(1<<i))&&(edg[i]&s)){
                    c[s|(1<<i)]=true;
                }
            }
        }
    }
}
int main(){
    int t;
    scanf("%d",&t);
    for(int cas=1;cas<=t;cas++){
        memset(edg,0,sizeof edg);
        memset(dp,0,sizeof dp);
        scanf("%d%d",&n,&m);
        for(int i=0,x,y;i<m;i++){
            scanf("%d%d",&x,&y);
            edg[x]|=(1<<y);
            edg[y]|=(1<<x);
            u[i]=x;
            v[i]=y;
        }
        init();
        int cnt=0;
        for(int s=0;s<(1<<n);s++){
            int r=(~s)&((1<<n)-1);
            if(s>r)break;
            if(c[s]&&c[r])
                dp[s]=dp[r]=1,cnt++;
        }
        for(int b=0;b<n;b++){
            for(int s=0;s<(1<<n);s++){
                if(!(s&(1<<b)))dp[s]+=dp[s|(1<<b)];
            }
        }
        printf("Case #%d:",cas);
        for (int i = 0; i < m; i++) {
            printf(" %d",cnt-dp[(1<<u[i])|(1<<v[i])]);
        }
        printf("
");
    }
    return 0;
}
原文地址:https://www.cnblogs.com/charles1999/p/12508658.html