Pieces Assignment
Source : zhouguyue | |||
Time limit : 1 sec | Memory limit : 64 M |
Submitted : 539, Accepted : 190
Background
有一个n*m的棋盘(n、m≤80,n*m≤80)要在棋盘上放k(k≤20)个棋子,使得任意两个棋子不相邻(每个棋子最多和周围4个棋子相邻)。求合法的方案总数。
Input
本题有多组测试数据,每组输入包含三个正整数n,m和k。
Output
对于每组输入,输出只有一个正整数,即合法的方案数。
Sample Input
2 2 3 4 4 1
Sample Output
0 16
题目链接:HITOJ 2662
由于实验室内大神讲了高大上的状压DP,于是就就去看了一下,感觉还是比较好理解的,就是感觉状态转移对于我这种还没入门的就比较难想到了……
dp[i][j][k]表示当前已遍历过i行,总共用了j个棋子,且最后一行摆放状态为k的方案数,然后就是如何进行状态的转移呢?首先DP总得要初始化的吧,先考虑只有一行的情况(实际上初始化的时候可以把所有合法的行状态都保存下来方便之后遍历),一行里的方案数就只需要初始化dp[1][j][k1]
把一行看成m个位置,每一个位置都是0或1,1表示放了棋子,0表示没放,比如一行3个棋子,可以是这样101、110、010、000、111(二进制)等等,如何判断当前摆放情况是否合法呢,这里要用到位运算&,即当前的状态转换成10进制进行&运算,sta&(sta<<1),可以发现若存在两个或以上1相邻的情况,按位与的结果必定不为0即不合法,然后在不知道如何摆放的情况下遍历所有可能的方案,上面的例子三个位置一共有2*2*2=8种情况,分别是011、101、110、100、010、001、111、000,但其实把n位全部填满最多只会达到2n-1因此可以for (i=0; i<=(1<<3)-1; ++i),然后把每一个行状态判断一下并保存在合法状态的集合里,然后对每一行都进行判断,
转移方程就是dp[i][j][k1]+=dp[i-1][j-这一行二进制状态所含1的个数count][k2](j>=count&&k1与k2不冲突)
代码:
#include <stdio.h> #include <iostream> #include <algorithm> #include <cstdlib> #include <sstream> #include <cstring> #include <bitset> #include <string> #include <deque> #include <stack> #include <cmath> #include <queue> #include <set> #include <map> using namespace std; #define INF 0x3f3f3f3f #define CLR(arr,val) memset(arr,val,sizeof(arr)) #define LC(x) (x<<1) #define RC(x) ((x<<1)+1) #define MID(x,y) ((x+y)>>1) typedef pair<int,int> pii; typedef long long LL; const double PI=acos(-1.0); const int M=1<<9; LL dp[82][22][M];//第i行、用了j个棋子、状态为第k个 int status[M]; inline bool check(const int &a,const int &b) { return (a&b)==0;//这里要加括号,==的优先级比&高 } int main(void) { int n,m,k,i,j,pre,cur; while (~scanf("%d%d%d",&n,&m,&k)) { CLR(dp,0); if(m>n)//m保持较小 swap(n,m); int legalcnt=0; int totalsta=(1<<m)-1; for (i=0; i<=totalsta; ++i) { if(check(i,i>>1)) { dp[1][bitset<10>(i).count()][legalcnt]=1LL; status[legalcnt++]=i; } } for (i=2; i<=n; ++i) { for (j=0; j<=k; ++j) { for (cur=0; cur<legalcnt; ++cur) { for(pre=0; pre<legalcnt; ++pre) { int curuse=bitset<10>(status[cur]).count(); if(check(status[pre],status[cur])&&j>=curuse) { dp[i][j][cur]+=dp[i-1][j-curuse][pre]; } } } } } LL r=0; for (i=0; i<legalcnt; ++i) r+=dp[n][k][i]; printf("%lld ",r); } return 0; }