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

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

在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\n" , 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("\n") ;

    }

    for( i = 1 ; i <= N ; i ++)

        printf("%d\n" , 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\n" , 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\n" , 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" , 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\n",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

 

你可能感兴趣的:(压缩)