P1896 [SCOI2005] 互不侵犯 题解
1. 题目大意
在 的棋盘里面放 个国王,使他们互不攻击,共有多少种摆放方案。国王能攻击到它上下左右,以及左上左下右上右下八个方向上附近的各一个格子,共 个格子。
注意是8个方向。
对于全部数据,,。
从这个数据范围看出是状压动规,暴搜明显超时。
2. 思路分析
现在知道是状压动规,考虑如何建立状态、转移方程、边界。
-
状态
首先,,可以想到用一个长 位的二进制串来表示状态。
思考一下转移必须的条件,可以把每一行作为一个阶段(于是有了第一维),还需要知道这一行的选取情况(第二维),此外,还要知道已经用掉的国王数量(第三维)。
于是有了具体的状态定义:令
dp[i][j][k]
表示第 行,使用 的方法安放,已经用了 个国王。为了方便后续的判断,也节省时间,可以把一行中所有合法的状态全部处理出来,存入数组中。
-
转移方程
明显,要从第 个状态转移过来。
枚举所有的当前状态和上一个状态,再枚举已经用去的国王数,把符合条件的累加起来。得到转移方程:
基本完成,还有一个小问题——怎么判断两行状态是否合法?
-
上下
很好处理,把两个状态取 ,如果答案不等于0,说明不可行。如图:
-
对角
考虑把一个状态左移一个,就转化为了上下的情况。如图:
于是有了上下和对角的判断函数:
1
2
3
4
5
6//x,y是相邻两行的状态
bool check(int x,int y){
if((x&y)||((x<<1)&y)||(x&(y<<1)))
return false;
return true;
}-
左右
上面提到可以通过预处理确定一行中左右的合法情况,这里具体说明:直接用 dfs 预处理可以的情况,写法如下:
1
2
3
4
5
6
7
8
9
10
11
12
13//d表示当前处理的列号,tot表示使用的国王总数,val表示建立起的状态二进制串
void init(int d,int tot,int val){
if(d>n){
num++;
a[num]=val;
v[num]=tot;
return;
}
init(d+1,tot,val);
//这一格空着
init(d+2,tot+1,val|(1<<(d-1)));
//这一格填上,直接通过+2跳过下一个,避免相邻
}
-
-
边界
明显,第一行所有状态都是 。
-
答案
易得:
3. 注意事项
- 会爆
int
,要开long long
! - 注意位运算优先级,保险起见加上括号!
4. code
1 |
|
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 shicj's blog!
评论