浅谈二进制拆分(蒟蒻已弃坑

来源:互联网 发布:网络文明志愿宣言 编辑:程序博客网 时间:2024/06/05 17:51

       先扔一个题:                               

                                                   棋盘上的车

【题目描述】

在n*n(n≤20)的方格棋盘上放置n 个车,求使它们不能互相攻击的方案总数。

【输入格式】

一行一个正整数n。

【输出格式】

一行一个正整数,即方案总数。

【样例输入】

3

【样例输出】

6

      其实这道题还可以用组合数学做,即方案数为(n!)个。复杂度O(n).

      这可以算是二进制拆分的模板题,二进制拆分主要适用对象就是有数量较少的物品,每个物品都有两种选择(选或不选),那么我们就可以用二进制拆分的做法来维护物品选择的情况的方案,具体实现是开一个2^n即1<<n-1大小的数组,其中数组类型是视方案数而定的,如果方案数需要用long long储存那数组类型就开long long。它的优势是操作方便;就此题而言,一个状态像000110101的方案数以直接从放一个棋子就转移到此状态的状态如000010101,000100101等方案的方案数相加得到。举段代码:如果f【i】表示状态为i时的方案数,那么f【i】可写为

for(int j=0;j<n;j++){if(i&(1<<j)){f[i]+=f[i-(1<<j)];}}
        其中i&(1<<j)是判断状态为i的第j为是否有车存在,如果有车就加上这一位没有车的方案数(在第j位放上车有f[i-(1<<j)]种方案)。边界是f【0】=1(一个不放时有一种情况),然后输出时输出f【1<<n-1】就可以了。

接下来就亮出此题的代码:

#include<cstdio>#include<iostream>#include<algorithm>using namespace std;const int MAXN=1<<20;unsigned long long f[MAXN];int n;int main(){//freopen("rook.in","r",stdin);//freopen("rook.out","w",stdout);scanf("%d",&n);int s=(1<<n)-1;for(int i=1;i<=s;i++) f[i]=0;f[0]=1;for(int i=1;i<=s;i++){for(int j=0;j<n;j++){if(i&(1<<j)){f[i]+=f[i-(1<<j)];}}} cout<<f[s];return 0;}

这就是这道题的二进制拆分的做法。

下面看一道难度略微提高的变种:

                                                                          放国王

【题目描述】

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

【输入格式】

输入文件有一行两个正整数,即n,k

【输出格式】

输出一行一个正整数,即方案总数。

【样例输入】

2 1

【样例输出】

4

             

      可能有的初学者看到这道题先搞了一个状态数组,然后就没有然后了……

       首先,这道题和上一道题不同的是,不明确当前行放了多少个国王,所以我们需要记录一下当前行的编号和所放国王数量:f[12][1<<11][110]。然后再进行状态转移;当然在转移之前需判断一下此种状态是否合法.
      具体做法是枚举该行i与上一行的状态j,然后判断((i>>1)&j)||((i<<1)&j)||(i&j)
其中i>>1判断的是j的右下是否有国王,i<<1判断的是j的左下角是否有国王,i判断的是j正下是否有国王;如果返回值为true,表明当前状态不合法,continue即可。

下面亮出此题代码:


#include<iostream>#include<cstdio>#include<cstring>#include<algorithm>#include<cstdlib>using namespace std;long long n,K,k,S[1000],num[1000],s0=0,ans=0;long long f[12][520][111];int main(){//freopen("placeking.in","r",stdin);//freopen("placeking.out","w",stdout);scanf("%d%d",&n,&K);for (int i=0;i<1<<n;++i) { if (i&(i<<1)) continue; k=0; for (int j=0;j<n;++j)  if (i&(1<<j)) k++; S[++s0]=i; num[s0]=k; }f[0][1][0]=1;for (int i=1;i<=n;i++) for (int t=1;t<=s0;++t) { int s1=S[t]; for (int l=0;l<=K;++l) { if (!f[i-1][t][l]) continue; for (int j=1;j<=s0;++j)  {  int s2=S[j];  if ((s1&s2)||(s1&(s2<<1))||(s1&(s2>>1))) continue;  if (num[j]+l>K) continue;  f[i][j][l+num[j]]+=f[i-1][t][l];  } } }for (int i=1;i<=s0;++i) ans+=f[n][i][K];cout<<ans;return 0;}

我暂时先扔两道题,将会不间断更新


自己的坑还得自己填qwq;

类似题目还有:

神奇的长方形骨牌覆盖,POJ 2441安排公牛,NOIP2005过河等。


原创粉丝点击