状态压缩学习笔记 (1)
book
归档:
OI
flag
[COGS 1516] 棋盘上的车
题目描述
原题地址:COGS
思路一
乘法原理水过,计算$n!$即可,复杂度$O(n)$
思路二
DP方程
每次放置一枚棋子,一行一行放,可以用一个二进制数表示状态,如$(0010011)_2$表示第一、二、五列已放棋子。
设$f(n)$是放置$n$枚棋子的方案数,可以知道,$f((0010011)_2)=f((0000011)_2)+f((0010001)_2)+f((0010010)_2)$,类似地,我们可以推出方程:
$$
\begin{cases}
f(n)=0&n=0
f(n)=\sum_{i\subset n}f(i)&n\not=0
\end{cases}
$$
注意,此处的$i\subset n$的表示是不严谨的。
代码
#include <bits/stdc++.h>
using namespace std;
long long f[1 << 21];
int n;
int main()
{
freopen("rook.in", "r", stdin);
freopen("rook.out", "w", stdout);
cin >> n;
f[0] = 1;
for (int i = 1; i <= (1 << n); ++i) // 枚举所有状态
{
for (int j = 0; j < n; ++j) // 枚举所有列
if (i & (1 << j)) // 当该列为1
f[i] += f[i - (1 << j)]; // (1 << j) 是一个只有一个1的二进制数,i - (1 << j) 是比i的二进制表示正好少一个1的数
}
cout << f[(1 << n) - 1] << endl;
}
[COGS 654] 特殊方格棋盘
题目描述
原题地址:COGS
思路
此题我不会用数学方法,只能状压DP。
DP方程不变,原理类似,只是多了一些不能放的位置。
使用一个数组a来记录不能放的位置。
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int n, m;
ll f[1 << 21];
ll a[21], ans[1 << 21];
inline int lowbit(int k) { return k & -k; }
int main()
{
freopen("examone.in", "r", stdin);
freopen("examone.out", "w", stdout);
cin >> n >> m;
for (int i = 1; i <= m; ++i)
{
int x, y;
cin >> x >> y;
a[x] |= (1 << (y - 1)); // 将a[x]的值设置为(1 << (y - 1))
}
ans[0] = 1;
for (int i = 1; i <= (1 << n) - 1; ++i)
{
int l = 0, d = i;
for (; d; d -= lowbit(d), ++l) ; // 此处使用lowbit以确定当前已经放了几个棋子,l为已放的棋子数
ll st = i & (~a[l]); // 如果此处已经被钦定,则st = 0,否则st = i。
for (int j = st; j; j -= lowbit(j))// 遍历i的所有“子集”
ans[i] += ans[i & ~lowbit(j)]; // MAGIC!:i & ~lowbit(j)与i相比刚好少了一个1
}
cout << ans[(1 << n) - 1] << endl;
}
也可以这样写,风格与第一题类似:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int n, m;
ll ans[1 << 21];
int a[21];
int lowbit(int k) { return k & -k; }
int main()
{
freopen("examone.in", "r", stdin);
freopen("examone.out", "w", stdout);
cin >> n >> m;
for (int i = 1; i <= m; ++i)
{
int x, y;
cin >> x >> y;
a[x] |= (1 << (y - 1));
}
ans[0] = 1;
for (int i = 1; i <= (1 << n); ++i)
{
int l = 0, d = i;
for (; d; d -= lowbit(d), ++l) ;
for (int j = 0; j < n; ++j)
if (i & (1 << j) && !((1 << j) & a[l])) // 如果当前位置不与障碍冲突,且在i中存在
ans[i] += ans[i - (1 << j)]; // 转移
}
cout << ans[(1 << n) - 1] << endl;
}
参考资料
最后感谢mk的讲解
navigate_before
状态压缩学习笔记 (2)
[COGS 2722][CodeforcesEduR22C] The tag game
navigate_next