暴力搜索 | DFS 回溯剪枝

「算法笔记」「上机指南」买来一个多月了。还是在自己胡乱coding,今天本打算看一下我并不熟的最小堆emmmmmm打开书发现好像图论也忘差不多了,决定从深搜看起吧。记录一下,(还有防走神。蜜汁炖鱿鱼有毒QAQ

  • 参考书是晴神的《算法笔记》

深搜的思想

  • 每到一个分叉口,按一定的顺序选择一条路往下走,每次都走到「死路」再回到上一个分叉口,再按顺序选择下一条路……
    “一定的顺序 ”可以是下个结点标号大小之类的
  • 栈(递归方式其实是利用了系统栈)
    进入一条路是“压栈”,回到分叉口是“出栈”。

背包问题

n件物品,每件物品重量为w[i],价值为v[i]。选出若干物品放入容量为V的背包,使得总重量不超过V的前提下,背包内物品总价值最大。

  • 理解:每件物品都有放入/不放入两条路,那么总共有2^n条路。死路是物品总重量超出V,或者n个物品的选择全部结束。及时更新当前最大总价值。
  • 剪枝:根据题目条件节省DFS的计算量(物品总重超出V就不再深入)
#include 
#include 

using namespace std;
const int MAXN = 51;
int nn, wi[MAXN], vi[MAXN], volume, value = 0;

void DFS(int index, int sumW, int sumV) {
    if (index == nn) return;
    if (sumW + wi[index] <= volume) {
        value = max(sumV + vi[index], value);
        DFS(index + 1, sumW + wi[index], sumV + vi[index]);
    }
    DFS(index + 1, sumW, sumV);
}

int main() {
    scanf("%d%d", &nn, &volume);
    for (int i = 0; i < nn; ++i)
        scanf("%d", &wi[i]);
    for (int i = 0; i < nn; ++i)
        scanf("%d", &vi[i]);
    DFS(0, 0, 0);
    printf("%d\n", value);
    return 0;
}

上述问题的推广

给定一个序列,枚举这个序列的所有子序列(可以不连续)

题设可以是
  • 寻找(某种特征最优的)子序列
  • 枚举N个整数中选择K个的所有方案
递归中需要传递的信息 可能有
  • 当前已经处理的元素个数 (正要处理的元素下标)
  • 现在已选择的元素个数
  • 与特征相关的值
  • 给定N个整数,从中选择K个,使他们之和为X。若有多种方案,选择其中元素平方和最大的。
#include 
#include 

using namespace std;
const int MAXN = 51;
int nn, kk, xx, num[MAXN], max_sq_sum = -1;
vector temp_res, res;

void DFS(int index, int chose, int sum, int sq_sum) {
    if (chose == kk && sum == xx) { // 保留结果再return 要放在剪枝之前!!!
        if (sq_sum > max_sq_sum) {
            max_sq_sum = sq_sum;
            res = temp_res;
        }
        return;
    }
    if (index == nn || chose > kk) return; //若限定正数 || sum > X
    temp_res.push_back(index);
    DFS(index + 1, chose + 1, sum + num[index], sq_sum + num[index] * num[index]);
    temp_res.pop_back();
    DFS(index + 1, chose, sum, sq_sum);
}

int main() {
    scanf("%d%d%d", &nn, &kk, &xx);
    for (int i = 0; i < nn; ++i)
        scanf("%d", &num[i]);
    DFS(0, 0, 0, 0);
    if (res.empty()) puts("No answer~");
    else
        for (auto item : res) {
            printf("%d ", num[item]);
        }
    return 0;
}
  • 给定N个整数,从中选择K个(每个数可以重复选择),使他们之和为X。若有多种方案,选择其中元素平方和最大的。
    因为可以重复选择,只需把“选择下标index”的分支改成不将index直接➕1即可。
#include 
#include 

using namespace std;
const int MAXN = 51;
int nn, kk, xx, num[MAXN], max_sq_sum = -1;
vector temp_res, res;

void DFS(int index, int chose, int sum, int sq_sum) {
    if (chose == kk && sum == xx) { // 保留结果再return 要放在剪枝之前!!!
        if (sq_sum > max_sq_sum) {
            max_sq_sum = sq_sum;
            res = temp_res;
        }
        return;
    }
    if (index == nn || chose > kk) return; //若限定正数 || sum > X
    temp_res.push_back(index);
    DFS(index, chose + 1, sum + num[index], sq_sum + num[index] * num[index]);
    temp_res.pop_back();
    DFS(index + 1, chose, sum, sq_sum);
}

int main() {
    scanf("%d%d%d", &nn, &kk, &xx);
    for (int i = 0; i < nn; ++i)
        scanf("%d", &num[i]);
    DFS(0, 0, 0, 0);
    if (res.empty()) puts("No answer~");
    else
        for (auto item : res) {
            printf("%d ", num[item]);
        }
    return 0;
}
  • A1103 Integer Factorization
    题目描述

    ️factor的power预先计算并保存,重复计算耗时间多。
    ️factor从大到小,就已经保证了先找到的解比后找到的大,不需要在factorSum相同时,再比较序列大小。(测试点2)
#include 
#include 
#include 

using namespace std;
int NN, KK, PP, bound, max_fac_sum = -1, pow_fac[21] = {0};
vector res, temp;

void init() {
    for (int i = 1; i <= bound; ++i) {
        pow_fac[i] = int(pow(i, PP));
    }
}

void DFS(int curr_factor, int currK, int pow_sum, int fac_sum) {
    if (pow_sum == NN && currK == KK) {
        if (fac_sum > max_fac_sum) {
            max_fac_sum = fac_sum;
            res = temp;
        }
        /*
         * factor从大到小,就已经保证了先找到的解比后找到的大
        else if (fac_sum == max_fac_sum) {
            for (int i = 0; i < KK; ++i) {
                if (temp[i] < res[i]) break;
                if (temp[i] > res[i]) {
                    res = temp;
                    break;
                }
            }
        }
        */
    }
    if (curr_factor <= 0 || currK >= KK || pow_sum > NN) return;
    temp.push_back(curr_factor);
    DFS(curr_factor, currK + 1, pow_sum + pow_fac[curr_factor], fac_sum + curr_factor);
    temp.pop_back();
    DFS(curr_factor - 1, currK, pow_sum, fac_sum);
}

int main() {
    scanf("%d%d%d", &NN, &KK, &PP);
    bound = int(pow(NN, 1.0 / PP)) + 1;
    init();
    DFS(bound, 0, 0, 0);
    if (res.empty()) puts("Impossible");
    else {
        printf("%d = %d^%d", NN, res[0], PP);
        for (int i = 1; i < KK; ++i) {
            printf(" + %d^%d", res[i], PP);
        }
    }
    return 0;
}

你可能感兴趣的:(暴力搜索 | DFS 回溯剪枝)