转载请注明:http://blog.csdn.net/jiangshibiao/article/details/23732795
【原题】
1087: [SCOI2005]互不侵犯King
Time Limit: 10 Sec Memory Limit: 162 MBSubmit: 1326 Solved: 767
[ Submit][ Status]
Description
在N×N的棋盘里面放K个国王,使他们互不攻击,共有多少种摆放方案。国王能攻击到它上下左右,以及左上左下右上右下八个方向上附近的各一个格子,共8个格子。
Input
只有一行,包含两个数N,K ( 1 <=N <=9, 0 <= K <= N * N)
Output
方案数。
Sample Input
Sample Output
【分析】一开始以为是深搜~~记得USACO里的八皇后n=13呢,这里才9.但是很快我就发现这是不明智的。经各路大神指点:这是状态压缩的DP。
【过程】翻遍了网上的题解,但是大多都过于简略,像我这种蒟蒻根本看不懂。于是开始自己YY。
首先,因为是状压DP,方程的某一维一定是一个状态。n<=9,我们可以用01串表示某一行放置的情况。2^0~2^8,那么总共状态就是2^9-1。空间还是承受的起。当然还要枚举一下当前做到第几行,以及当前一共放了几颗棋子。很快,有关f的表示就出来了:用f[i][j][now]表示到第i行,一共放j个棋子(包括这之前的),且第i行的状态是k的方案数。再考虑转移。这一行肯定是由上一行的状态转移过来的,那么我们可以再枚举上一行的状态。
很自然的,发现这会超时。每次枚举一种状态就需要2^9,两重循环已经快爆掉了!我们可以发现一件事情。比如n=5,我们每次枚举到的11111,11011,10111,01011这些状态都是无效的!那么我们可以先预处理一下对于每一行的所有可行的状态(就是不能有连续的1)。这样的效率仍然不高——我们还可以对于每种可行的状态i,j,预处理i和j是否能够相邻,这样我们在DP的时候,就可以O(1)来转移了!
【代码】
#include<cstdio>
#define STEP 512
using namespace std;
long long f[10][101][STEP],ans;
int m,n,num,i,j,now,k,stay[101],cnt[101]; //stay记录每种状态压缩后的值,cnt记录对应的状态中1的个数。
bool map[101][101];
void dfs(int k,int put,int now) //预处理,k是放了几颗,put是当前放的位置,now是当前状态压缩后的值。
{
stay[++m]=now;cnt[m]=k;
if (k>=(n+1)/2||k>=num) return;
for (int i=put+2;i<=n;i++)
dfs(k+1,i,now+(1<<(i-1)));
}
void init()
{
dfs(0,-1,0); //预处理出每种状态,共有m种。
for (int i=1;i<=m;i++)
for (int j=1;j<=m;j++) //枚举某两种状态是否能相邻。一共有三种不能的情况:上下都是1,左上、右下是1,左下、右上是1。
map[i][j]=map[j][i]=((stay[i]&stay[j])||((stay[i]>>1)&stay[j])||((stay[i]<<1)&stay[j]))?0:1; for (int i=1;i<=m;i++)f[1][cnt[i]][i]=1ll; //边界
}
int main()
{
scanf("%d%d",&n,&num);
init();
for (i=2;i<=n;i++)
{
for (j=0;j<=num;j++)
{
for (now=1;now<=m;now++)
{
if (cnt[now]>j) continue;
for (k=1;k<=m;k++)
if (map[k][now]&&cnt[k]+cnt[now]<=j) f[i][j][now]+=f[i-1][j-cnt[now]][k]; //转移
}
}
}
for (i=1;i<=m;i++)
ans+=f[n][num][i];
printf("%I64d",ans);
return 0;
}