状态压缩动态规划---P1896 [SCOI2005]互不侵犯
题目
题目描述
在N×N的棋盘里面放K个国王,使他们互不攻击,共有多少种摆放方案。国王能攻击到它上下左右,以及左上左下右上右下八个方向上附近的各一个格子,共8个格子。
输入格式
只有一行,包含两个数N,K ( 1 <=N <=9, 0 <= K <= N * N)
输出格式
所得方案数
输入#1
3 2
输出#1
16
思路
-
看给定的数据范围不大,棋盘9*9,国王最多放81个,可以考虑动态规划
-
在表示国王放的位置的时候,可以使用二进制表示的方式.这也是状态压缩动态规划常用的表示方式.
假设目前棋盘为样例中的3*3.
对于每行,能够摆放的国王情况数:
0B000~0B111,即从全部不放直到全部都放上国王,共8中情况. -
对于当前行摆放国王的情况中,不是所有都是合适的.因为题目中有限制:
国王能够攻击它的上下左右
因为能够攻击左右,跟当前行本身有关系,所以需要排除它.
对于任意一种摆放i,可以进行以下过滤:(i&(i>>1)) == 0 && (i&(i<<1))==0
获得符合左右攻击不到的要求.
-
因为题目中要求是:
国王能攻击到它上下左右,以及左上左下右上右下八个方向
所以对于除了左右其它六个方向,需要引入其它行,和本行进行对比.
我们是从上到下摆放,所以对于当前行,只要考虑不攻击到上方已经摆放好的国王即可.不需要考虑下方的情况.
我们假设上一行的某一个国王摆放状态为b,当前行的摆放状态为a.
除了当前行本身符合左右不互相攻击之外,
还需要满足以下要求:(a&b) == 0;//当前行摆放的所有国王的上方不存在攻击对象 (a&(b<<1)) == 0;//当前行摆放的所有国王的左上方不存在攻击对象 (a&(b>>1)) == 0;//当前行摆放的所有国王的右上方不存在攻击对象
-
最后,题目给定了国王数量限制K,所以对于摆放的国王们,最终能够摆放的数量不可以超过k.
-
状态表示与状态转移
根据上面的分析,应当明白,需要知道- 当前行
- 上一行
- 当前行的摆放状态
- 上一行的摆放状态
- 使用的国王数量
这些量,来表示我们的状态和状态之间的转移.
假设当前行是i,
那么上一行可以用i-1表示.
对于摆放的国王,需要进行枚举这一行和上一行.这个没有像当前行和上一行一样,有明确的加减关系,需要分别枚举.
对于使用的国王数量,可以计算1的个数,来进行累计,最多为k.
所以,可以使用
dp[i][j][p]
表示
- 在第i行
- 这一行的国王摆放情况为j
- 总计使用的国王数量为p
的情况下,摆放方式的总和.
假设上一行某一次摆放的国王情况为m,那么状态转移可以写成:
dp[i][j][p] += dp[i-1][m][k-p]
-
最后,只要统计最后一行,枚举这一行当的摆放情况,将dp结果求和,即可.
代码
java版本:
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;
public class Main {
public static void main(String[] args) throws Exception {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StringTokenizer st = new StringTokenizer(br.readLine());
int n = Integer.parseInt(st.nextToken());
int k = Integer.parseInt(st.nextToken());
List<Integer> rightStatus = getRightStatus(n);
long[][][] dp = new long[n + 1][rightStatus.size()][k + 1];
int tempOneCnt;
for (int i = 0; i < rightStatus.size(); i++) {
tempOneCnt = getOneCnt(rightStatus.get(i));
if (tempOneCnt <= k) {
dp[1][i][tempOneCnt] = 1;
}
}
int currentLineStatus, preLineStatus;
for (int a = 2; a <= n; a++) {
for (int b = 0; b < rightStatus.size(); b++) {
currentLineStatus = rightStatus.get(b);
for (int c = 0; c < rightStatus.size(); c++) {
preLineStatus = rightStatus.get(c);
if ((currentLineStatus & preLineStatus) != 0) {
continue;
}
if ((currentLineStatus & (preLineStatus << 1)) != 0) {
continue;
}
if ((currentLineStatus & (preLineStatus >> 1)) != 0) {
continue;
}
for (int d = 0; d <= k; d++) {
if (d - getOneCnt(currentLineStatus) >= 0) {
dp[a][b][d] += dp[a - 1][c][d - getOneCnt(currentLineStatus)];
}
}
}
}
}
long result = 0;
for (int i = 0; i < rightStatus.size(); i++) {
result += dp[n][i][k];
}
System.out.println(result);
}
private static int getOneCnt(int num) {
int cnt = 0;
char[] binaryNum = Integer.toBinaryString(num).toCharArray();
for (char i : binaryNum) {
if (i == '1') {
cnt++;
}
}
return cnt;
}
private static List<Integer> getRightStatus(int n) {
int allStatus = (int) Math.pow(2, n);
List<Integer> rightStatus = new ArrayList<>();
for (int i = 0; i < allStatus; i++) {
if ((i & (i >> 1)) == 0 && (i & (i << 1)) == 0) {
rightStatus.add(i);
}
}
return rightStatus;
}
}
c++版本:
#define _CRT_SECURE_NO_WARNINGS
typedef long long ll;
#include <vector>
#include <iostream>
#include <cstring>
using namespace std;
int N, M, arrangeCnt;
int getOneCnt(int num) {
int temp = 1;
int cnt = 0;
while (temp <= num)
{
if ((temp & num) == temp) {
cnt++;
}
temp = (temp << 1);
}
return cnt;
}
int main() {
cin >> N >> M;
arrangeCnt = 1;
arrangeCnt = (arrangeCnt << N);
ll dp[10][1<<10][10*10];//dp[i][j][k]:在i行,j的位置排列下,并且从1~i行总计使用了k个国王时,满足情况的排列数
//ll dp[4][8][3];
memset(dp, 0, sizeof(dp));
int oneCnt=0;
for (int i = 0; i < arrangeCnt; i++) {
if ((i & (i >> 1)) == 0 && (i & (i<<1)) == 0) {
oneCnt = getOneCnt(i);
if (oneCnt <= M) {
dp[1][i][oneCnt] = 1;
}
}
}
for (int i = 2; i <= N; i++) {//当前行
for (int j = 0; j < arrangeCnt; j++) {//当前行的所有排列情况
if ((j & (j >> 1)) != 0 || (j & (j << 1)) != 0) {
continue;
}
for (int k = 0; k < arrangeCnt; k++) {//前一行的所有排列情况
if ((k & (k >> 1)) != 0 || (k & (k << 1)) != 0) {
continue;
}
if ((j & k)!=0) {
continue;
}
if ((j & (k << 1)) != 0) {
continue;
}
if ((j & (k >> 1)) != 0) {
continue;
}
for (int l = 0; l < M + 1; l++) {
if (l - getOneCnt(j) >= 0) {//保证数组表示的含义中的l个国王,最多全部使用,对于超出规定的数量是不符合实际情况的,排列的计数是不能算在结果中的
dp[i][j][l] += dp[i - 1][k][l-getOneCnt(j)];
}
}
}
}
}
ll sum = 0;
for (int i = 0; i < arrangeCnt; i++) {
sum += dp[N][i][M];
}
cout << sum;
return 0;
}