Pieces Assignment
Source : zhouguyue | |||
Time limit : 1 sec | Memory limit : 64 M |
Submitted : 684, Accepted : 235
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
/* 对于每一行,如果把没有棋子的地方记为0,有棋子的地方记为1,那么每一行的状态都可以表示成一个2进制数,进而将其转化成10进制。 那么这个问题的状态转移方程就变成了 设dp[ i ] [ j ][k ]表示当前到达第i列,一共使用了j个棋子,且当前行的状态在压缩之后的十进制数为k 时的状态总数。那么我们也可以类似的写出状态转移方程: dp[ i ][ j ][ k ]=sum( dp[ i-1][ j-num(k) ][ w ] ) num(k)表示 k状态中棋子的个数,w表示前一行的状态。 虽然写出了状态转移方程,但是还是有很多细节问题需要解决:比如,如何保证当前状态是合法的? 最基本的做法是:首先判断k状态是否合法,也就是判断在这一行中是否有2个旗子相邻,然后枚举上一行的状态w,判断w状态是否合法,然后判断k状态和w状态上下之间是否有相邻的棋子。 当然这样做的时间复杂度是很高的,也就是说有很多地方可以优化. 比如:判断每一行状态是否合法,可以在程序一开始判断然后保存结果,判断k状态和w状态上下之间是否有相邻的棋子,可以利用位运算,if(k&w)说明上下之间有相邻的棋子等等。 */ #include<iostream> #include<cstdio> #include<cstring> using namespace std; int n,m,k,sum[1<<9],cnt,mark[1<<9]; long long dp[81][21][1<<9],ans;//dp[i][j][k]到第i行,共用了j个棋子,状态为k int count(int x){ int res=0; while(x){ if(x&1)res++; x>>=1; } return res; } int main(){ freopen("Cola.txt","r",stdin); while(scanf("%d%d%d",&n,&m,&k)!=EOF){ ans=0;cnt=0; memset(dp,0,sizeof(dp)); if(m>n)swap(m,n); for(int i=0;i<(1<<m);i++) if(!(i&(i<<1))){ sum[++cnt]=count(i); mark[cnt]=i; dp[1][sum[cnt]][cnt]=1; } for(int i=2;i<=n;i++) for(int j=0;j<=k;j++) for(int t=1;t<=cnt;t++) for(int p=1;p<=cnt;p++) if(!(mark[t]&mark[p])&&j>=sum[t]){ dp[i][j][t]+=dp[i-1][j-sum[t]][p]; } for(int i=1;i<=cnt;i++){ ans+=dp[n][k][i]; } printf("%lld ",ans); } }