洛谷-P1896 [SCOI2005]互不侵犯(状压DP || DFS)

题目描述

在N×N的棋盘里面放K个国王,使他们互不攻击,共有多少种摆放方案。国王能攻击到它上下左右,以及左上左下右上右下八个方向上附近的各一个格子,共8个格子。

注:数据有加强(2018/4/25)

输入格式

只有一行,包含两个数N,K ( 1 <=N <=9, 0 <= K <= N * N)

输出格式

所得的方案数

输入 #1

3 2

输出 #1

16

解题思路:

DP解法:

首先考虑每一层内部的合法状态,由于N<10 也就是说,用二进制位表示一个点的状态,那么状态数最多也就1024个。由于他可以攻击相邻的棋,也就是二进制位中不能有连续的1.
这个可以直接预处理处理出来,记录所有的合法状态数。以及这个状态数中对应的1的个数。因为题目只给了k个棋子。
然后考虑层与层之间的关系。考虑从上往下逐层考虑。那么新的一层就只有前一层可以影响到他。根据题目要求,就是两层状态做与运算等于0,左移和右移一位再与还是等于0,就是合法的可以共存的。那么新的一层就加上上一层的状态数。
最后还有一个地方需要考虑的就是已经放了的棋子的总数,因为题目要求放k个棋子。那么每个dp值都应该记录下当前对应的棋子总数。增加一层时,也就增加id【sta】个棋子。
代码看似三层循环复杂度很高,其实预处理合法状态的时候就去掉了一大半的状态,所以复杂度是完全可以接受的。

DFS解法:

第一步同DP解法,先预处理出所有的合法状态,以及1的个数,然后枚举第一层,依次增加一层,判断条件也相同,最后枚举第n层时,如果sum为k,则返回1。
DFS代码更简洁但是需要注意的是需要记忆化,不然会进行大量的重复搜索导致TLE。记忆化时,也是需要记忆三个参数,层数,状态数和棋子总和。加上简单的剪枝,就可以AC了。

AC代码(DP):

#include <bits/stdc++.h>
#define int long long
using namespace std;
int dp[10][100][1024];	// 三维分表代表当前层数,当前放棋子数,当前状态数
int legal[1024];		// 记录合法状态
int id[1024];			// 记录状态数对应的1的个数

signed main()
{
    memset(legal,0,sizeof(legal));
    memset(id,0,sizeof(id));
    int n,m;
    cin>>n>>m;
    for(int i = 0 ; i < 1024 ; i ++)	// 预处理出一层所有的合法状态
    {
       if(((i<<1)&i) == 0 && ((i>>1)&i) == 0)
          legal[i] = 1;
       int cnt =0;
       for(int j = i ; j != 0 ; j >>= 1)
           if(j&1) 		cnt ++;
       if(cnt > m) 		legal[i] = 0;
       else 			id[i] = cnt;
    }
    for(int i = 0 ; i < (1<<n) ; i ++)
        if(legal[i])
            dp[1][id[i]][i] = 1;
    for(int i = 2 ; i <= n ;i ++)	// 枚举层数
        for(int j = 0 ; j < (1<<n) ; j ++)	// 枚举本层状态
            if(legal[j])
                for(int sta = 0; sta < (1<<n) ;sta ++)	// 枚举上一层状态
                    if(legal[sta] && (j&sta) == 0  && ((j<<1)&sta) == 0 && ((j>>1)&sta) == 0 )
                        for(int k = 0 ; k <= m-id[j] ; k ++)
                            dp[i][k+id[j]][j] += dp[i-1][k][sta];
    int ans = 0;
    for(int j = 0 ; j < (1<<n); j ++) // 计算最后方案数总和
        ans += dp[n][m][j];
    cout<<ans<<endl;
}


AC代码(DP):

#include <bits/stdc++.h>
#define int long long
using namespace std;
int dp[10][100][1024];		// 记忆化数组
int legal[1024];			// 合法状态
int id[1024];
int n,m;

int dfs(int level,int sta,int sum) 
{
    if(sum > m || level > n) return 0;
    if(level == n-1 && sum == m) return 1;  // 剪枝
    if(dp[level][sum][sta] != -1) return dp[level][sum][sta];  // 已经搜索过的直接返回
    int res = 0;
    for(int i = 0 ; i < (1<<n) ; i ++) // 枚举第level+1层的状态
    {
        if(legal[i] && (i&sta) == 0 && ((sta<<1)&i) == 0 && ((sta>>1)&i) == 0)
            res += dfs(level+1,i,sum + id[i]);
    }
    dp[level][sum][sta] = res;
    return res;
}

signed main()
{
    memset(legal,0,sizeof(legal));
    memset(dp,-1,sizeof(dp));
    memset(id,0,sizeof(id));
    cin>>n>>m;
    for(int i = 0 ; i < 1024 ; i ++)	// 预处理出一层所有的合法状态
    {
        if(((i<<1)&i) == 0 && ((i>>1)&i) == 0)
            legal[i] = 1;
        int cnt = 0;
        for(int j = i ; j != 0 ; j >>= 1)
            if(j&1)     cnt ++;
        if(cnt > m)     legal[i] = 0;
        else            id[i] = cnt;
    }
    int ans = 0;
    for(int i = 0 ; i < (1<<n) ; i ++) // 枚举第1层状态
        if(legal[i])
            ans += dfs(0,i,id[i]);
    cout<<ans<<endl;
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值