• 暑假集训Day2 互不侵犯(状压dp)


    这又是个状压dp大型自闭现场

    题目大意:

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

    输入格式:

    只有一行,包含两个数N,K 。

    输出格式:

    所得的方案数。

    算法分析:

    1.显然这又是一道状压的题
    2.显然一样是用f数组表示方案数

    But 这个f数组需要开三维

    为什么呢
    我们首先分析一下f的转移情况 f的状态与什么有关呢 首先我们很容易知道我们的dp是从上往下一点点递推实现的 而这个国王会攻击到自己的身旁八个位置
    所以呢 那f就会对自己的下一行产生影响 而且只会对自己的下一行产生影响 换而言之 f只和自己上一行的状态有关
    这样的话我们就会用到两维,一维存储前i行 另一维存储状态 但是看到这个题只用两维是不够的 因为这个题需要放置的国王个数恰好等于K
    所以我们将前i行放置的国王个数作为一维
    和平时的状压题一样我们将状态作为最后一维 所以关于f[i][j][k]的定义我们有 前i行共放了j个国王而且第i行所放国王的状态为k 的方案数
    所以我们就可以写出这样的状态转移方程

    int sum(int x){
    	int cnt = 0;
    	for(int i = x;i;i-=lowbit(i))cnt++;
    	return cnt;
    }
    
    for(int i = 1;i <= n;++i)
    		for(int j = 0;j < maxs;++j){//枚举本行状态
    			if(j & (j<<1))continue;//如果该状态冲突则跳过
    			for(int k = 0;k < maxs;++k){//枚举上一行状态
    				if(j & k || j & (k << 1) || j & (k>>1))continue;//如果当前行状态和上一行状态冲突则跳过
    				for(int s = sum(j);s <= m;++s)//枚举前i行所放的国王个数
    					f[i][s][j] += f[i-1][s - sum(j)][k];//+=上一行的成立状态数
    			}
    		}
    

    这个代码的注释已经很清晰了
    但是我们再具体分析一下思想(如果不理解sum函数的去翻暑假集训day2 特殊方格棋盘
    首先我们枚举行数
    然后枚举本行的状态
    本行状态与自己冲突的情况就是(j & (j<<1))为真 j有两位二进制位同时为1 那么才可能(j&j<<1)为真
    举个栗子:j = 01100 那么j<<1就是11000和j取与后并不等于0 所以冲突(即连续两个格子放置了国王,他们互相攻击)

    3.然后就要枚举上一行的状态
    关于本行与上一行状态冲突的判定具体方法可参见上一条

    最后的累加:

    划重点: sum(j) 为当前行所放的国王个数,前i行的国王个数肯定就是大于等于这个数的 枚举前i行国王个数,然后减去sum(j)就是前i-1行共放的国王个数
    因此f[i-1][s-sum(j)][k] 就是 前i-1行共s-sum(j)个国王并且第i-1行的国王个数为k的方案数
    通过这个的累加就是我们的递推过程

    代码

    #include<bits/stdc++.h>
    using namespace std;
    long long f[10][100][1000],ans;//f[i][j][k]表示前i行放j个国王并且当前行状态为k的成立方案数
    int lowbit(int x){return x & -x;}
    
    int sum(int x){
    	int cnt = 0;
    	for(int i = x;i;i-=lowbit(i))cnt++;
    	return cnt;
    }
    
    int main(){
    	int n,m;scanf("%d%d",&n,&m);
    	f[0][0][0] = 1;
    	int maxs = 1<<n;
    	for(int i = 1;i <= n;++i)
    		for(int j = 0;j < maxs;++j){//枚举本行状态
    			if(j & (j<<1))continue;//如果该状态冲突则跳过
    			for(int k = 0;k < maxs;++k){//枚举上一行状态
    				if(j & k || j & (k << 1) || j & (k>>1))continue;//如果当前行状态和上一行状态冲突则跳过
    				for(int s = sum(j);s <= m;++s)//枚举前i-1行所放的国王个数
    					f[i][s][j] += f[i-1][s - sum(j)][k];//+=上一行的成立状态数
    			}
    		}
    	for(int i = 0;i < 1<<n;++i)ans += f[n][m][i];//ans+=前n行放m个国王并且当前行状态为i
    	printf("%lld
    ",ans);
    	return 0;
    }
    

    点点关注
    谢谢观看>)<

  • 相关阅读:
    [Linux]Ubuntu下正确挂载NTFS磁盘的方法
    Google搜索指令与自定义引擎
    【Linux】Android手机在Ubuntu上无法被adb识别解决办法(权限相关)
    [ Linux ] Remove PPA source from your pc
    一键去除 UC浏览器 论坛模式 内置的广告
    Huawei U8825d 对4G手机内存重新分区过程[把2Gb内置SD卡容量划分给DATA分区使用]
    【Android】把Linux GCC安插在Android手机上
    [Windows]隐藏文件及目录的命令
    Linux压缩包简体中文乱解决方案[全]
    修改su密码 macbook
  • 原文地址:https://www.cnblogs.com/2004-08-20/p/13189560.html
Copyright © 2020-2023  润新知