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

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

在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

你可能感兴趣的:(状态压缩 周伟论文+代码+例题)