【SCOI2005】【BZOJ1087】互不侵犯King(状压dp)
problem
- 在N×N的棋盘里面放K个国王
- 每个国王会攻击它周围的一圈共8个格子
- 使他们互不攻击,共有多少种摆放方案
- N <= 9
solution
- 用01串表示某一行放置的情况
- 首先枚举当前做到第几行,以及当前一共放了几颗棋子。
- 于是状态f[i][j][k]表示到第i行,一共放j个棋子(包括这之前的),且第i行的状态是k的方案数。
- 再考虑转移。这一行肯定是由上一行的状态转移过来的,那么我们可以再枚举上一行的状态。
- 很自然的,发现这会超时。每次枚举一种状态就需要2^9,两重循环已经快爆掉了!我们可以发现一件事情。比如n=5,我们每次枚举到的11111,11011,10111,01011这些状态都是无效的。那么我们可以先预处理一下对于每一行的所有可行的状态(就是不能有连续的1)。
- 这样的效率仍然不高——我们还可以对于每种可行的状态i,j,预处理i和j是否能够相邻,这样我们在DP的时候,就可以O(1)来转移了。(这里也可以不预处理,每次直接判断ij能否相邻也可。)
最后,记得开long long。
codes
#include<iostream>
using namespace std;
const int maxn = 512;
typedef long long LL;
int c1[maxn], cnt[maxn], c2[maxn][maxn];
LL ans, f[10][100][maxn];
int main(){
int n, m;
cin>>n>>m;
int all = (1<<n)-1;
for(int i = 0; i <= all; i++){
if((i&(i>>1))==0){
c1[i] = 1;
for(int x = i; x; x >>= 1) cnt[i]+= (x&1);
}
}
for(int i = 0; i <= all; i++)if(c1[i])f[1][cnt[i]][i] = 1;
for(int i = 1; i < n; i++){
for(int j = 0; j <= all; j++)if(c1[j]){
for(int k = 0; k <= all; k++)if(c1[k]){
if(((j&k)==0)&&((j&(k>>1))==0)&&((j&(k<<1))==0)){
for(int p = cnt[j]; p+cnt[k]<=m; p++)
f[i+1][p+cnt[k]][k] += f[i][p][j];
}
}
}
}
for(int i = 0; i <= all; i++)ans += f[n][m][i];
cout<<ans<<"\n";
return 0;
}