蓝桥杯练习-3.3
代码练习
2n皇后问题
问题描述
给定一个n*n的棋盘,棋盘中有一些位置不能放皇后。现在要向棋盘中放入n个黑皇后和n个白皇后,使任意的两个黑皇后都不在同一行、同一列或同一条对角线上,任意的两个白皇后都不在同一行、同一列或同一条对角线上。问总共有多少种放法?n小于等于8。
输入格式
输入的第一行为一个整数n,表示棋盘的大小。
接下来n行,每行n个0或1的整数,如果一个整数为1,表示对应的位置可以放皇后,如果一个整数为0,表示对应的位置不可以放皇后。
输出格式
输出一个整数,表示总共有多少种放法。
样例输入
4
1 1 1 1
1 1 1 1
1 1 1 1
1 1 1 1
样例输出
2
样例输入
4
1 0 1 1
1 1 1 1
1 1 1 1
1 1 1 1
样例输出
0
思路:
2n皇后问题属于抽象DFS,其本质特点跟n皇后(8皇后)问题一样,但是这里需要摆俩种皇后。
可以先用n皇后的办法,先将黑皇后全部摆好,摆好的第一次,就可以引入白皇后了,在黑皇后在棋盘上摆好的基础上,我们去摆白皇后,摆白皇后的方法和n皇后一样,只多了一个限制条件—有些地方黑皇后占据了,我们在这个地方进行剪枝即可,当白皇后也搜完了所以行,总方法数就可以加1。
代码实现:
#include <iostream>
using namespace std;
int map1[10][10];
int n;
int ans = 0;//ans记录一共有多少放皇后的方法
bool col1[10],x1[20],x2[20],col2[10],y1[20],y2[20];
bool checkB(int r, int i)//检查黑皇后是否能放在r行i列的函数
{
return !col1[i] && !x1[r + i] && !x2[r - i + 8] && map1[r][i];//规避棋盘本身为0的地方
}
bool checkW(int r, int i)//检查白皇后是否能放在r行i列的函数
{
return !col2[i] && !y1[r + i] && !y2[r - i + 8] && map1[r][i];//之前把黑皇后占的地方变成了0,这里既规避了棋盘本 //身的0,又规避了黑皇后占的地方
}
void dfsW(int r);//要先声明下这个函数,否则黑皇后里面用不了
void dfsB(int r)//搜索黑皇后,这里传入的参数是当前搜索黑皇后时在棋盘的第几行
{
if (r == n + 1)//如果黑皇后1轮所有行全部放完,就可以放白皇后了
{
dfsW(1);//放白皇后
return;//也要注意当搜完所有行的时候,这个函数就结束了(只是这个分支结束了)还有其他分支
}
for (int i = 1; i <= n; i++)//在这一行中一列一列的搜
{
if (checkB(r, i))
{
col1[i] = x1[r + i] = x2[r - i + 8] = true;
map1[r][i] = 0;//黑皇后占的地方由1变为0,白皇后占不了
dfsB(r + 1);//如果找到了可以放的,想都不用想,直接进入下一行,这里就会产生一个第一行选择第i列的分支情况,当然,
//当这个分支结束了,过了好久,才会又回到这里,先是回溯,再重新for循环,因为此时才刚刚i++
col1[i] = x1[r + i] = x2[r - i + 8] = false;//回溯
map1[r][i] = 1;//回溯
}
}
return;//所有分支都搜完了,结束函数
}
void dfsW(int r)//搜索白皇后
{
if (r == n + 1)
{
ans++;//当白皇后搜完全行时,这时总方法数加1
return;
}
for (int j = 1; j <= n; j++)
{
if (checkW(r, j))
{
col2[j] = y1[r + j] = y2[r - j + 8] = true;
dfsW(r + 1);
col2[j] = y1[r + j] = y2[r - j + 8] = false;//回溯
}
}
return;
}
int main()
{
cin >> n;
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= n; j++)
{
cin >> map1[i][j];
}
}
dfsB(1);//开始搜索黑皇后摆放位置
cout << ans << endl;
return 0;
}
❕注意:
函数类型是void类型,也是可以加return的
但是其后面不能加任何数值 表示函数到此为止
视频学习
背包问题
• 01背包
当前有N件物品和一个容积为V的背包。已知第i件物品的体积是weight[i],价值是value[i]。由于每种物品有且仅有一件,因此只能选择放或者不放,我们称之为01背包问题。
这个问题的状态,考虑最后一步,最后一步就是放第i个物品,前i个物品,放在背包里,总重量不超过w的前提下,所获得的最大价值为dp[i] [w], dp[i] [w] 表示前 i 件物品放入容量为 w 的背包中可获得的最大价值
是否将第i个物品装入背包中,就是决策,为了使价值最大化,
∀
j
<
C
i
,
d
p
[
i
]
[
j
]
=
d
p
[
i
−
1
]
[
j
]
\forall j<Ci,dp[i][j] = dp[i-1][j]
∀j<Ci,dp[i][j]=dp[i−1][j]
d p [ i ] [ j ] = m a x ( d p [ i − 1 ] [ j ] , d p [ i − 1 ] [ j − C i ] + W i ) dp[i][j]=max(dp[i-1][j],dp[i-1][j-Ci]+Wi) dp[i][j]=max(dp[i−1][j],dp[i−1][j−Ci]+Wi)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DfaVwv0d-1616140647128)(C:\Users\费泽涛\Desktop\图片\01背包问题图解.webp)]
参考代码:
for (int i = 1; i <= N; ++i) {
for (int j = 0; j <=V; ++j) {
if (j >= c[i]) {
dp[i][j] = max(dp[i - 1][j - c[i]] + w[i], dp[i - 1][j]);
} else {
dp[i][j] = dp[i][j-1];
}
}
}
根据之前已经引出的状态转移方程,我们再来理解一遍,对于编号为 i 的物品:
- 如果选择它,那么,当前背包的最大价值等于” i 号物品的价值“ 加上 ”减去 i 号物品占用的空间后剩余的背包空间所能存放的最大价值“,即dp[i] [j] = value[i] + dp[i-1] [j-weight[i]];
- 如果不选择它,那么,当前背包的价值就等于前 i-1 个物品存放在背包中的最大价值,即 dp[i] [j] = dp[i-1] [j]
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YS678Yz1-1616140647130)(C:\Users\费泽涛\Desktop\图片\7896890-eba68adb166bd81a.webp)]
dp[i] [j] 的结果取两者的较大值,即:
dp[i][j] = max(value[i] + dp[i-1][j-weight[i]], dp[i-1][j])
代码实现:
#include <iostream>
#include <algorithm>
#include <string>
using namespace std;
int dp[21][1010];
int w[21],v[21];//w表示物品的体积,v表示物品的价值
int main()
{
int N, V;
cin >> N >> V;
for (int i = 1; i <= N; i++)
{
cin >> v[i] >> w[i];
}
for (int i = 1; i <= N; i++)
{
for (int j = 0; j <= V; j++)
{
if (w[i] <= j)
dp[i][j] = max(dp[i - 1][j - w[i]] + v[i], dp[i - 1][j]);
else
dp[i][j] = dp[i - 1][j];
}
}
cout << dp[N][V] << endl;
return 0;
}
• 01背包-压缩空间(优化空间复杂度)
观察上面的代码,会发现,当更新dp[i] […]时,只与dp[i-1] […]有关
所以可以把dp数组只开成一维表示体积,然后从大到小枚举体积,为什么要反向遍历来更新dp[]的值呢,因为最新的状态转移方程为:
d
p
[
j
]
=
m
a
x
(
d
p
[
j
−
w
e
i
g
h
t
[
i
]
]
+
v
a
l
u
e
[
i
]
,
d
p
[
j
]
)
dp[j] = max(dp[j - weight[i]] + value[i], dp[j])
dp[j]=max(dp[j−weight[i]]+value[i],dp[j])
可以看到,dp[j]的值是有可能直接被大于他的dp[j - weight[i]] + value[i]覆盖掉,如果后续需要dp[j]的值的话,那么这个值已经被更新了,而不是之前dp[j]的值。而从大到小,大的即使被更新了,也不会对接下来造成影响。一定要让 j - weight[i]那个地方还没有被更新的情况下拿来用。
for (int i = 1; i <= n; ++i) {
for (int j = V; j >= weight[i]; --j) {
dp[j] = max(dp[j - weight[i]] + value[i], dp[j]);
}
}
代码实现:
#include <iostream>
#include <algorithm>
using namespace std;
int dp[1010];
int w[21], v[21];//w代表体积,v代表物品价值
int main()
{
int N, V;
cin >> N >> V;
for (int i = 1; i <= N; i++)
{
cin >> v[i] >> w[i];
}
for (int i = 1; i <= N; i++)
{
for (int j = V; j >= w[i]; j--)
{
dp[j] = max(dp[j - w[i]] + v[i], dp[j]);
}
}
cout << dp[V] << endl;
return 0;
}