• 高维前缀和(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;
    }
    
  • 相关阅读:
    Window7中Eclipse运行MapReduce程序报错的问题
    Hadoop以及其外围生态系统的安装参考
    《node.js开发指南》第五章与新版Node变化太大的一些问题
    打造开发React Native的Sublime
    幸福
    近日阅读记录
    react中属性默认值是true?
    git撤销操作总结
    React Native中的DeviceEventEmitter.addListener与DeviceEventEmitter.emit
    React父子组件的一个混淆点
  • 原文地址:https://www.cnblogs.com/charles1999/p/12508658.html
Copyright © 2020-2023  润新知