「算法笔记」「上机指南」买来一个多月了。还是在自己胡乱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;
}