• 状态压缩 周伟论文+代码+例题


    所有题解报告可以在周伟论文中看到 但是没有代码  下面是我自己的或者从网上搜集到的代码

    在n*n(n≤20)的方格棋盘上放置n 个车(可以攻击所在行、列),求使它们不
    能互相攻击的方案总数。
    仅供和我一样的菜鸟们参考

    #include <iostream>
    #include <cmath>
    using namespace std;
    __int64 a[1100000];
    int main()
    {
        __int64 n;
        while (cin >> n){
              memset(a, 0, sizeof(a));
              a[0] = 1;
              for (int i = 1; i <= 1<<n; i ++){//注意这里是1左移n位不是n<<1,显然这里是在枚举0000~1111的每一种状态
                  for (int j = i; j > 0; j -= (j&-j)){//首先注意这里是倒推,因为要由之前的状态推出现在的状态。
                      a[i] += a[i&~(j&-j)];  //这里的位运算处理甚是漂亮,它可以保证每一次都刚好取到i的子集 ,首先j&-j可以得出在i之前的每一种状态j的最低位1的位置k,然后取反可以保证只有第k个位置刚好为0,那么求与之后就在原来i的基础上去除了第k个1
                  //比如说当前i枚举到0111,那么j&-j = 0001,则~(j&-j) = 1110,那么i&1110 = 0110,0110就是0111的一个子集,随后去掉当前最低位k,j变成0110,以此反复运算,直到j=0000
                  }   
              }
              cout<<a[(1<<n)-1]<<endl;     
        } 
        return 0;   
    }

    另外的一个代码

    #include<stdio.h>
    #include<string.h>
    const int MAXN = 1 << 20 ;
    __int64 f[MAXN + 23] ;
    int main() {
        int N ;
        while( scanf("%d" , &N) != EOF) {
            memset( f , 0 , sizeof(f)) ;
            int i , tmp = (1 << N) - 1 ;
            f[0] = 1 ;
            for( i = 1 ; i <= tmp ; i ++) {
                int tt = i , x ;
                while( tt != 0 ) {
                    x = tt&(-tt) ;
                    f[i] += f[i ^ x] ;
                    tt ^= x ;
                }
            }
            printf("%I64d
    " , f[tmp] ) ;
        }
        return 0 ;
    }


    前一个状态压缩的升级版
    在n*n(n≤20)的方格棋盘上放置n 个车,某些格子不能放,求使它们不能互相攻击的方案总数。
    和前面差别不大,主要是加了一个不可达点的限制,那么用二进制记录每一个不可达点然后还是按照以前的进行位运算,我写的代码不知道对不对,只核对了3以下的数据,希望路过神牛予以点评或修改

    #include<stdio.h>
    #include<stdlib.h>
    #include<string.h>
    #include<time.h>
    const int MAX_N = 1 << 8 ;
    __int64 f[MAX_N] ;
    int map[23][23] , a[23] ;
    int N ;
    void init() {
        int i , j ;
        srand( time ( NULL )) ;
        for( i = 1 ; i <= N ; i ++) {
            a[i] = 0 ;
            for( j = 1 ; j <= N ; j ++) {
                //scanf("%d" , &map[i][j] ) ;
                map[i][j] = rand() % 2 ;
                a[i] = a[i]*2 + map[i][j] ;
                printf("%d " , map[i][j] ) ;
            }
            printf("
    ") ;
        }
        for( i = 1 ; i <= N ; i ++)
            printf("%d
    " , a[i] ) ;
    }
    void solve() {
        int i , tmp = (1 << N) - 1 ;
        memset( f , 0 , sizeof( f )) ;
        f[0] = 1 ;
        for( i = 1 ; i <= tmp ; i ++ ) {
            int tt = i , j ;
            int one[23] , top = 0 ;
            while( tt != 0 ) {
                one[++top] = tt&(-tt) ;
                tt ^= one[top] ;
            }
            for( j = 1 ; j <= top ; j ++) {
                if( ( one[j] & a[top] ) == 0 )
                    f[i] += f[i^one[j]] ;
            }
        }
        printf("%I64d
    " , f[tmp] ) ;
    }
    int main() {
        while( scanf("%d" , &N) != EOF) {
            init() ;
            solve() ;
        }
        return 0 ;
    }


    另外一个代码      2个代码 一起   防止看不懂

    #include <iostream>
    #include <cmath>
    using namespace std;
    __int64 a[1100000];
    int is_allow[20];//用位记录所有不能放置棋子的位置
    int counter[21][21];
    /*void turn(int x, int n)//转换成二进制输出,属测试代码
    {
         int t = x;
         int num= 0;
         int xx[100];
         while (x){
               xx[num++] = x%2;
               x/=2;
         }    
         for (int i = num; i < n; i ++)cout<<0;
         for (int i = num-1; i >=0 ; i --)cout<<xx[i];
        
         cout<<endl;
    }*/
    void chang(int n)//将输入的地图转化到is_allow中
    {
         for (int i = 1; i <= n; i ++){
             int sum = 0;
             for (int j =n; j >= 1; j --){//转化成十进制存储到is_allow中
                 sum += counter[i][n-j+1]*(int)pow(2.0, n-j);
             }   
             is_allow[i] = sum;
         }        
    }
    int main()
    {
        __int64 n;
        while (cin >> n){
              memset(is_allow, 0, sizeof(is_allow));
              memset(counter, 0, sizeof(counter));
              int t;
              scanf("%d", &t);
              for (int i = 0; i < t; i ++){
                  int x, y;
                  scanf("%d%d", &x, &y);
                  counter[x][y] = 1;//记录不能放置棋子的位置
              }
              chang(n);//转化
              memset(a, 0, sizeof(a));
              a[0] = 1;
              for (int i = 1; i <= 1<<n; i ++){
                  int cc = 0, tt = i;
                  while (tt)tt&=(tt-1),cc ++;//cc记录当前放到了第几行
                  int tmps = i^is_allow[cc];//在i中去除不能放置的位置
                  for (int j = tmps; j > 0; j -= (j&-j)){
                      a[i] += a[i&~(j&-j)];
                  } 
              }
              printf("%I64d", a[(1<<n)-1]);  
        } 
        return 0;   
    }


      给出一个n*m 的棋盘(n、m≤80,n*m≤80),要在棋盘上放k(k≤20)个棋子,使得任意两个棋子不相邻。 求使得任意两个棋子不相邻的放置方案数。


    这个题目的状态压缩模型是比较隐蔽的。观察题目给出的规模,n、m≤80,这个规模要想用 SC 是困难的,若同样用上例的状态表示方法(放则为 1,不放为0),2^80  无论在时间还是在空间上都无法承受。然而我们还看到 n*m≤80,这种给出数据规模的方法是不多见的,有什么玄机呢?能把状态数控制在可以承受的范围吗?稍微一思考,我们可以发现:9*9=81>80,即如果n,m 都大于等于 9,将不再满足n*m≤80 这一条件。所以,我们有n 或m 小于等于 8,而2^8 是可以承受的。我们假设 m≤n(否则交换,由对称性知结果不变)n  是行数 m  是列数,则每行的状态可以用 m 位的二进制数表示。但是本题和例 1 又有不同:例 1 每行每列都只能放置一个棋子,而本题却只限制每行每列的棋子不相邻。但是,上例中枚举当前行的放置方案的做法依然可行。我们用数组 s[1..num] 保存一行中所有的num 个放置方案,则s 数组可以在预处理过程中用DFS 求出,同时用c[i]保存第i 个状态中 1 的个数以避免重复计算。开始设计状态。如注释一所说,维数需要增加,原因在于并不是每一行只放一个棋子,也不是每一行都要求有棋子,原先的表示方法已经无法完整表达一个状态。我们用 f[i][j][k]表示第 i 行的状态为s[j]且前i 行已经放置了k 个棋子(2) 的方案数。沿用枚举当前行方案的做法,只要当前行的方案和上一行的方案不冲突即可,“微观”地讲,即s[snum[i]]和s[snum[i-1]]没有同为 1 的位,其中snum[x]表示第x 行的状态的编号。然而,虽然我们枚举了第 i 行的放置方案,但却不知道其上一行(i-1)的方案。为了解决这个问题,我们不得不连第i-1 的状态一起枚举,则可以写出递推式:

                         f[0][1][0]=1;
                         f[i][j][k]=∑f[i-1][p][k-c[j]]


    其中s[1]=0,即在当前行不放置棋子;j和p是需要枚举的两个状态编号,且要求s[j]与s[p]不冲突,即s[j]&s[p]=0。(3) 当然,实现上仍有少许优化空间,例如第i行只和第i-1行有关,可以用滚动数组节省空间。

    #include<stdio.h>
    #include<string.h>
    int s[263] , c[263] , top  ;
    int f[10][36][10] ;
    int N , M , pn ;
    // 产生一行里所有的合法状态
    void DFS( int t , int state , int count , int* flag) {
        if( t == M )
        {
            s[++top] = state ;
            c[top] = count ;
            return ;
        }
        flag[t + 1] = 0 ;
        DFS( t + 1 , state*2 , count , flag ) ;
        if( flag[t] == 0 ) 
        {
            flag[t + 1] = 1 ;
            DFS( t + 1 , state*2 + 1 , count + 1 , flag) ;
        }
    }
    void solve() {
        memset( f , 0 , sizeof( f )) ;
        f[0][1][0] = 1 ;
        int i , j , k , p ;
        for( i = 1 ; i <= N ; i ++) {
            for( j = 1 ; j <= top ; j ++) {
                for( p = 1 ; p <= top ; p ++) {
                    for( k = c[j] ; k <= pn ; k ++) {
                        if( (s[j]&s[p]) == 0 ) {
                            f[i][j][k] += f[i - 1][p][k - c[j]] ;
                        }
                    }
                }
            }
        }
        int sum = 0 ;
        for( i = 1 ; i <= top ; i ++) {
            sum += f[N][i][pn] ;
        }
        printf("%d
    " , sum ) ;
    }
    int main() {
        while( scanf("%d %d %d" , &N , &M , &pn ) != EOF) {
            int  flag[23] = { 0 };
            top = 0 ;
            if( N < M ) {
                N ^= M ;
                M ^= N ;
                N ^= M ;
            }
            printf("%d %d
    " , N , M ) ;
            DFS( 0 , 0 , 0 , flag) ;
            solve() ;
        }
        return 0 ;
    }



    内容参考于zhang360896270

    yang_7_46

    结合例题  SGU 223 

    【例3

    n*n(n≤10)的棋盘上放k个国王(可攻击相邻的8个格子),求使它们无法互相攻击的方案数

    http://acm.sgu.ru/problem.php?contest=0&problem=223

    思路看周伟的论文    代码献上

    #include<stdio.h>
    #include<string.h>
    int n,pn;
    int s[1<<11],c[1<<11],top;
    long long f[11][1<<11][111];
    
    void DFS(int t,int state,int count,int flag[])
    {
        if(t==n)
        {
            s[++top]=state;
            c[top]=count;
            return;
        }
        flag[t]=0;
        DFS(t+1,state*2,count,flag);
        if(t==0||flag[t-1]==0)
        {
            flag[t]=1;
            DFS(t+1,state*2+1,count+1,flag);
        }
    }
    /* 下面这个也对
    void dfs(int p,int last,int now,int cnt)
    {
        if (p == n){s[++top]=now;c[top]=cnt;return ;}
        dfs(p+1,0,now*2,cnt);
        if (!last) dfs(p+1,1,now*2+1,cnt+1);
    }*/
    void solve()
    {
        int i;
        memset(f,0,sizeof(f));
         for(i=1;i<=top;i++)
        {
            f[1][i][c[i]]=1;
        }
        for(i=2;i<=n;i++)
        //f[0][1][0]=1;这种方法一样
       // for (i=1;i<=n;++i)
        {
            for(int si=1;si<=top;si++)
            {
                for(int pi=1;pi<=top;pi++)
                {
                    for(int cnt=c[si];cnt<=pn;cnt++)
                    {
                        if((s[si]&s[pi])||(s[si]&(s[pi]>>1))||(s[si]&s[pi]<<1))
                           continue;
                        if(cnt-c[si]>=c[pi])
                         f[i][si][cnt]+=f[i-1][pi][cnt-c[si]];
                    }
                }
    
            }
        }
        long long sum=0;
        for(i=1;i<=top;i++)
        {
            sum+=f[n][i][pn];
        }
        printf("%lld
    ",sum);
    
    }
    int main()
    {
        int  flag[11];
        while(scanf("%d %d",&n,&pn)!=EOF)
        {
            top=0;
            memset(flag,0,sizeof(flag));
            DFS(0,0,0,flag);
           //dfs(0,0,0,0);
            solve();
        }
        return 0;
    }
    


     


     

    未完待续 hnust_xiehonghao

  • 相关阅读:
    linux安装nginx
    git配置多个SSH密钥
    webpack加载器安装node-sass失败的解决方法
    Vue-cli webpack打包之后index.html缺少引号的问题
    算法:一个数组中所有元素的最小公倍数
    JS 实现一个睡眠函数sleep
    剑指offer 牛客67道题集合
    剑指offer
    剑指offer
    剑指offer
  • 原文地址:https://www.cnblogs.com/javawebsoa/p/3231120.html
Copyright © 2020-2023  润新知