原题链接:
洛谷:https://www.luogu.com.cn/problem/P1896
AcWing:https://www.acwing.com/problem/content/description/1066/
难度:提高+/省选-(…)
涉及知识点:状态压缩DP
非常经典的状态压缩DP问题。定义 f [ i ] [ j ] [ k ] f[i][j][k] f[i][j][k] 为摆完前 i i i 行,摆了 j j j 个国王,状态为 k k k 的集合。考虑状态压缩,即用二进制数表示某一行的状态,例如全是 0 的话就是一个国王也没有摆,反之就是全部都摆了(当然这是不合法的状态)。由于题目给出了条件限制,我们就需要考虑当前的状态转移是不是合法的,怎样判定呢?如下两点:
那么对于上面说的第一、二种情况,可以使用位运算。对于第一种情况,使用与与运算;对于第二种运算,使用或运算后判断状态的合法性(左右有没有冲突)。
考虑提前预处理在某一行中从
0
∼
1
<
<
n
0\sim 1 << n
0∼1<<n 的所有状态的合法性,因为可能存在某一些状态自身就是不合法的,用一个
v
e
c
t
o
r
vector
vector 存储所有的合法状态,还要用一个数组记录当前的状态有多少个国王,以便于后面进行状态转移时在体积层(国王数量)判断是否是超过当前数量限制。
然后考虑初始的合法状态间能否进行合法的转移,即执行上文中的两个位运算操作,并且用一个
v
e
c
t
o
r
vector
vector 记录状态转移的关系。
最后再状态转移时,枚举每一个状态的可转移状态,判断数量会不会超过,做一下状态转移即可。用
a
a
a 表示当前状态,用
b
b
b 表示可转移状态,用
c
c
c 表示当前状态的国王数量,状态转移方程为:
f
[
i
]
[
j
]
[
a
]
+
=
f
[
i
]
[
j
−
c
]
[
b
]
f[i][j][a] += f[i][j - c][b]
f[i][j][a]+=f[i][j−c][b]
初始化
f
[
0
]
[
0
]
[
0
]
=
1
f[0][0][0] = 1
f[0][0][0]=1。
#include
#include
#include
using namespace std;
const int N = 12, M = 1 << 10, K = 110;
typedef long long LL;
int n, m;
int cnt[M];
vector<LL> head[M]; //某个状态可以转移过去的状态
vector<LL> state; //预处理的所有合法状态
LL f[N][K][M];
//某个状态是否合法
bool check(int x)
{
for (int i = 0; i < n; i++)
{
if ((x >> i & 1) && (x >> i + 1 & 1)) return false;
}
return true;
}
//统计某个状态的1的数量也就是这个状态下摆放的国王数量
int count(int x)
{
int res = 0;
for (int i = 0; i < n; i++) res += x >> i & 1;
return res;
}
int main()
{
cin >> n >> m;
for (int i = 0; i < 1 << n; i++)
{
//打表
if (check(i))
{
//是个合法状态
state.push_back(i);
cnt[i] = count(i); //摆放的国王数量
}
}
//检查某个状态能否向另一个状态转移
for (int i = 0; i < state.size(); i++)
{
for (int j = 0; j < state.size(); j++)
{
int a = state[i], b = state[j];
if ((a & b) == 0 && check(a | b))
{
head[i].push_back(j);
}
}
}
f[0][0][0] = 1; //初始化
for (int i = 1; i <= n +1 ; i++)
{
for (int j = 0 ; j <= m; j++)
{
for (int a = 0; a < state.size(); a++)
{
for (auto b : head[a])
{
int c = cnt[state[a]];
if (j >= c)
{
f[i][j][a] += f[i - 1][j - c][b];
}
}
}
}
}
cout << f[n + 1][m][0];
return 0;
}