蓝桥杯练习-3.3

蓝桥杯练习-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的
但是其后面不能加任何数值 表示函数到此为止

视频学习

2019年蓝桥杯训练营(C++) 13.1背包问题视频讲解

背包问题

• 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[i1][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[i1][j],dp[i1][jCi]+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[jweight[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;
}
已标记关键词 清除标记
相关推荐
©️2020 CSDN 皮肤主题: 1024 设计师:白松林 返回首页