状态压缩动态规划---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.

  • 状态表示状态转移
    根据上面的分析,应当明白,需要知道

    1. 当前行
    2. 上一行
    3. 当前行的摆放状态
    4. 上一行的摆放状态
    5. 使用的国王数量
      这些量,来表示我们的状态和状态之间的转移.
      假设当前行是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;
}
posted @ 2022-03-26 09:34  Monstro  阅读(55)  评论(0)    收藏  举报