所有题解报告可以在周伟论文中看到 但是没有代码 下面是我自己的或者从网上搜集到的代码
在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
结合例题 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; }