动态规划中的2
在动态规划中我们经常会遇到关于2的问题,即往往不是0,1两种状态
比如:不能超过2
这个时候我们可以考虑将0,1,2设为状态
状态转移为0,1,2三种状态相互转移的公式
中国象棋的炮
题意
给出 n ∗ m n*m n∗m的棋盘,,计算在棋盘上放炮的方案,使得棋盘上的炮不能相互攻击的到
分析
状态
显然,每行每列可以放置的炮的数量不能超过2
若我们知道每一列0,1,2的数量,然后我们就能按行转移得到方案数
一个想法是,用状压DP,用三进制表示每一列的状态
但显然复杂度肯定超了
但我们仔细想想,并不需要知道每一列的状态,只需要知道0的列数,1的列数,2的列数
我们记录为1的列数和为2的列数即可
状态转移方程
d
p
[
k
+
1
]
[
i
]
[
j
]
=
(
d
p
[
k
+
1
]
[
i
]
[
j
]
+
d
p
[
k
]
[
i
]
[
j
]
)
dp[k + 1][i][j] = (dp[k + 1][i][j] + dp[k][i][j]) % mod
dp[k+1][i][j]=(dp[k+1][i][j]+dp[k][i][j])
d
p
[
k
+
1
]
[
i
+
1
]
[
j
]
=
(
d
p
[
k
+
1
]
[
i
+
1
]
[
j
]
+
d
p
[
k
]
[
i
]
[
j
]
∗
(
m
−
i
−
j
)
)
dp[k + 1][i + 1][j] = (dp[k + 1][i + 1][j] + dp[k][i][j] * (m - i - j)) % mod
dp[k+1][i+1][j]=(dp[k+1][i+1][j]+dp[k][i][j]∗(m−i−j))
d
p
[
k
+
1
]
[
i
−
1
]
[
j
+
1
]
=
(
d
p
[
k
+
1
]
[
i
−
1
]
[
j
+
1
]
+
d
p
[
k
]
[
i
]
[
j
]
∗
i
)
dp[k + 1][i - 1][j + 1] = (dp[k + 1][i - 1][j + 1] + dp[k][i][j] * i) % mod
dp[k+1][i−1][j+1]=(dp[k+1][i−1][j+1]+dp[k][i][j]∗i)
d
p
[
k
+
1
]
[
i
+
2
]
[
j
]
=
(
d
p
[
k
+
1
]
[
i
+
2
]
[
j
]
+
d
p
[
k
]
[
i
]
[
j
]
∗
(
m
−
i
−
j
)
∗
(
m
−
i
−
j
−
1
)
/
2
)
dp[k + 1][i + 2][j] = (dp[k + 1][i + 2][j] + dp[k][i][j] * (m - i - j) * (m - i - j - 1) / 2) % mod
dp[k+1][i+2][j]=(dp[k+1][i+2][j]+dp[k][i][j]∗(m−i−j)∗(m−i−j−1)/2)
d
p
[
k
+
1
]
[
i
]
[
j
+
1
]
=
(
d
p
[
k
+
1
]
[
i
]
[
j
+
1
]
+
d
p
[
k
]
[
i
]
[
j
]
∗
(
m
−
i
−
j
)
∗
i
)
dp[k + 1][i][j + 1] = (dp[k + 1][i][j + 1] + dp[k][i][j] * (m - i - j) * i) % mod
dp[k+1][i][j+1]=(dp[k+1][i][j+1]+dp[k][i][j]∗(m−i−j)∗i)
d
p
[
k
+
1
]
[
i
−
2
]
[
j
+
2
]
=
(
d
p
[
k
+
1
]
[
i
−
2
]
[
j
+
2
]
+
d
p
[
k
]
[
i
]
[
j
]
∗
i
∗
(
i
−
1
)
dp[k + 1][i - 2][j + 2] = (dp[k + 1][i - 2][j + 2] + dp[k][i][j] * i * (i - 1)
dp[k+1][i−2][j+2]=(dp[k+1][i−2][j+2]+dp[k][i][j]∗i∗(i−1)
代码
#include <iostream>
#include <cstdio>
using namespace std;
typedef long long LL;
const LL mod = 9999973;
const int maxn = 105;
LL dp[maxn][maxn][maxn];
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
int n, m; LL sum = 0;
cin >> n >> m;
dp[0][0][0] = 1;
for (int k = 0; k < n; k++) {
for (int i = 0; i <= m; i++) {
for (int j = 0; i + j <= m; j++) {
dp[k + 1][i][j] = (dp[k + 1][i][j] + dp[k][i][j]) % mod;
dp[k + 1][i + 1][j] = (dp[k + 1][i + 1][j] + dp[k][i][j] * (m - i - j)) % mod;
dp[k + 1][i - 1][j + 1] = (dp[k + 1][i - 1][j + 1] + dp[k][i][j] * i) % mod;
dp[k + 1][i + 2][j] = (dp[k + 1][i + 2][j] + dp[k][i][j] * (m - i - j) * (m - i - j - 1) / 2) % mod;
dp[k + 1][i][j + 1] = (dp[k + 1][i][j + 1] + dp[k][i][j] * (m - i - j) * i) % mod;
dp[k + 1][i - 2][j + 2] = (dp[k + 1][i - 2][j + 2] + dp[k][i][j] * i * (i - 1) / 2) % mod;
}
}
}
for (int i = 0; i <= m; i++)
for (int j = 0; i + j <= m; j++)
sum = (sum + dp[n][i][j]) % mod;
cout << sum << '\n';
}
消防局的设立
题目描述
2020年,人类在火星上建立了一个庞大的基地群,总共有n个基地。起初为了节约材料,人类只修建了n-1条道路来连接这些基地,并且每两个基地都能够通过道路到达,所以所有的基地形成了一个巨大的树状结构。如果基地A到基地B至少要经过d条道路的话,我们称基地A到基地B的距离为d。
由于火星上非常干燥,经常引发火灾,人类决定在火星上修建若干个消防局。消防局只能修建在基地里,每个消防局有能力扑灭与它距离不超过2的基地的火灾。
你的任务是计算至少要修建多少个消防局才能够确保火星上所有的基地在发生火灾时,消防队有能力及时扑灭火灾。
分析
这是一道经典的树形DP例题
一般就是给你一棵数和每个点可向外覆盖的点的个数,求最少选几个点可以覆盖所有的点
该类题状态为一般为:1.第几号点
2.还可以向外覆盖多少点
本题可以外覆2个点,递推过程中公有5个状态
状态
d
p
[
i
]
[
0
]
dp[i][0]
dp[i][0]:覆盖其子树以及他本身、他的父亲以及他的父亲的父亲(爷爷?)
d
p
[
i
]
[
1
]
dp[i][1]
dp[i][1]:覆盖其子树以及它本身以及他的父亲
d
p
[
i
]
[
2
]
dp[i][2]
dp[i][2]:覆盖其子树以及他本身
d
p
[
i
]
[
3
]
dp[i][3]%
dp[i][3]:覆盖其所有子数
d
p
[
i
]
[
4
]
dp[i][4]
dp[i][4]:覆盖其所有儿子的子树
状态转移方程
i:表示当前节点 s:表示其儿子 k:表示其其他儿子
d
p
[
i
]
[
0
]
=
s
u
m
(
d
p
[
s
]
[
4
]
)
+
1
dp[i][0]=sum(dp[s][4])+1
dp[i][0]=sum(dp[s][4])+1
覆盖本身节点、其父亲以及爷爷需要在其本身设立新的消防站,则其儿子和孙子也已经被覆盖
则覆盖子树只需要将其孙子的子树都覆盖即可。也就是
s
u
m
(
d
p
[
s
]
[
4
]
)
sum(dp[s][4])
sum(dp[s][4])
d
p
[
i
]
[
1
]
=
m
i
n
(
d
p
[
s
]
[
0
]
+
s
u
m
(
d
p
[
k
]
[
3
]
)
,
d
p
[
i
]
[
0
]
)
dp[i][1]=min(dp[s][0]+sum(dp[k][3]),dp[i][0])
dp[i][1]=min(dp[s][0]+sum(dp[k][3]),dp[i][0])
覆盖其父亲,只要有一个子树能覆盖到其爷爷,
其他子树的本身节点也可以由该子树覆盖,其他子树只需要覆盖到其儿子即可。
d
p
[
i
]
[
2
]
=
m
i
n
(
d
p
[
s
]
[
1
]
+
s
u
m
(
d
p
[
k
]
[
2
]
)
,
d
p
[
i
]
[
1
]
)
dp[i][2]=min(dp[s][1]+sum(dp[k][2]),dp[i][1])
dp[i][2]=min(dp[s][1]+sum(dp[k][2]),dp[i][1])
覆盖其本身,只要有一个子树能覆盖到其父亲,
其他子树只需要覆盖到其本身即可。
d
p
[
i
]
[
3
]
=
m
i
n
(
s
u
m
(
d
p
[
s
]
[
2
]
)
,
d
p
[
i
]
[
2
]
)
dp[i][3]=min(sum(dp[s][2]),dp[i][2])
dp[i][3]=min(sum(dp[s][2]),dp[i][2])
覆盖到其儿子,只需要其所有子树覆盖到本身即可
d
p
[
i
]
[
4
]
=
m
i
n
(
s
u
m
(
d
p
[
s
]
[
3
]
)
,
d
p
[
i
]
[
3
]
)
dp[i][4]=min(sum(dp[s][3]),dp[i][3])
dp[i][4]=min(sum(dp[s][3]),dp[i][3])
覆盖到其孙子,只需要其所有子树覆盖到儿子即可
代码
#include <iostream>
#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std;
const int maxn = 1005;
const int inf = 0X3f3f3f3f;
int n;
vector<int> E[maxn];
int dp[maxn][5];
//0 为+2 dp[i][0]=sum(dp[s][4])
//1 为+1 dp[i][1]=min(dp[s][0]+sum(dp[k][3]),dp[i][0])
//2 为0 dp[i][2]=min(dp[s][1]+sum(dp[k][2]),dp[i][1])
//3 为-1 dp[i][3]=sum(dp[s][2])
//4 为-2 dp[i][4]=sum(dp[s][3])
void dfs(int x) {
dp[x][0] = 1;
dp[x][1] = dp[x][2] = dp[x][3] = dp[x][4] = 0;
for (int i = 0; i < E[x].size(); i++) {
dfs(E[x][i]);
dp[x][0] += dp[E[x][i]][4];
dp[x][3] += dp[E[x][i]][2];
dp[x][4] += dp[E[x][i]][3];
}
if (E[x].size() == 0) {
dp[x][1] = dp[x][2] = 1;
}
else{
for (int i = 0; i < E[x].size(); i++) {
dp[x][1] += dp[E[x][i]][3];
dp[x][2] += dp[E[x][i]][2];
}
int f1 = inf, f2 = inf;
for (int i = 0; i < E[x].size(); i++) {
f1 = min(f1, dp[E[x][i]][0] - dp[E[x][i]][3]);
f2 = min(f2, dp[E[x][i]][1] - dp[E[x][i]][2]);
}
dp[x][1] += f1;
dp[x][2] += f2;
}
for (int i = 1; i <= 4; i++)
dp[x][i] = min(dp[x][i], dp[x][i - 1]);
}
int main() {
scanf("%d", &n);
int f;
for (int i = 2; i <= n; i++) {
scanf("%d", &f);
E[f].push_back(i);
}
dfs(1);
printf("%d\n", dp[1][2]);
}