誰もが列挙アルゴリズムを学んだと思います。学んだら、Jun Jiaoから学んでください!
まずは質問を見てください〜
トピック1:完全な配置
- 説明
nを入力し、繰り返されない1からnまでのすべての順列、つまりnのすべての順列を出力します。 - 分析
明らかに、この問題はforループ列挙では実行できません。これは、「n-recycle」を使用する必要があり、nは変数であるため、列挙アルゴリズムを使用できないためです。
この問題を解決する方法は?ループ層の数が不定の場合、再帰によって実現できると誰もが考える必要があります。これが、深さ優先探索(dfs)アルゴリズムの目的の1つです。
再帰のプロセスでは、dfsアルゴリズムはforループを使用して列挙します。つまり、ここでのforループの層の数は、1つずつ書き込むのではなく、再帰によって決定されます。 - コード
#include <bits/stdc++.h>
using namespace std;
bool vis[11]; // 假设n最大为10,vis[i]表示第i个数在当前这个排列中是否被用过
int n, a[11]; // 表当前排列的结果
void dfs(int stp) {
// stp是step的缩写,表示现在已经枚举到了第几步(所谓的第几层循环)
if (stp == n + 1) {
// 当n层循环都枚举完后,输出当前结果并且结束dfs函数
for (int i = 1; i <= n; ++i) {
cout << a[i] << ' ';
}
cout << '\n'; // 不要忘记排列之间要用换行隔开
return ;
}
for (int i = 1; i <= n; ++i) {
// 枚举所有数,找到符合条件的数(没有被用过),然后放到当前数位上
if (!vis[i]) {
vis[i] = 1; a[stp] = i; // 先放到当前数位上,继续枚举,然后尝试其他数,这样的过程就是一个回溯算法
dfs(stp + 1);
vis[i] = 0;
}
}
}
int main() {
cin >> n;
dfs(1); // 从第1个数开始枚举
return 0;
}
コメントはすでに非常に明確であり、こんにゃく氏はこのトピックについてもう話しません。
こんにゃくジュンの暖かいリマインダー:検索と列挙のプロセス中に、質問の性質に応じてプロセスを整理して最適化できます(これについては次の数レッスンで説明します)。ただし、ほとんどの問題では、dfsの時間計算量が超過するため、検索する前に問題状態の総数を決定する必要があります。
みなさんへの練習用質問です〜
トピック2:ローストチキン
- 背景
豚ハンケは鶏肉を手に入れた - 説明
豚ハンケはローストチキンを食べるのが好きです(これは同じ動物です、なぜ揚げるのが緊急すぎるのですか!)ハンケは鶏肉を非常に特別に食べます、なぜそれは特別なのですか?具材は10種類(マスタード、クミンなど)なので、具材1〜3グラムでローストチキンの美味しさは全具材の合計です。
ハンケさんが教えてくれたら知りたくなります。美味しさ、これら10成分のマッチングスキームをすべて出力してください - 入力形式:
1行、n <= 5000 - 出力形式:
1行目、プログラムの総数、
2行目から最後まで、10個の数字。これは、各成分の品質が
辞書式順序で配置されていることを示します。
要件を満たす方法がない場合は、最初の行に「0」を出力するだけです。 - 入力例#1:
11 - 出力例#1:
10
1 1 1 1 1 1 1 1 1 2
1 1 1 1 1 1 1 1 2 1
1 1 1 1 1 1 1 2 1 1
1 1 1 1 1 1 2 1 1 1
1 1 1 1 1 2 1 1 1 1 1
1 1 1 1 2 1 1 1 1 1
1 1 1 2 1 1 1 1 1 1
1 1 2 1 1 1 1 1 1 1
1 2 1 1 1 1 1 1 1 1
2 1 1 1 1 1 1 1 1 1 - 分析
明らかに、この質問は10サイクルで列挙できますが、最も基本的なdfを学習しました。なぜ、10サイクルの列挙を使用するのでしょうか。Konjai Junがテンプレートを提供しますので、タイトルを書き終えてください。ここから送信できます。
#include <bits/stdc++.h>
using namespace std;
int n, ans1, ans2[10000][10], arr[10], sum;
void dfs(int stp, int now) {
// 请继续完成这个函数~
}
int main() {
cin >> n; dfs(1, 0);
cout << ans1 << '\n';
for (int i = 1; i <= ans1; ++i) {
for (int j = 1; j <= 10; ++j) {
cout << ans2[i][j] << ' ';
}
puts("");
}
return 0;
}
- 解釈
タイトル効果は
、n、10を出力するプログラムの種類数とnの数を入力し、順序は2種類とは見なされず、各オプションを出力します。
問題解決のアイデア
最初のレベル(最初の配列要素)から始めて、現在の配列の合計を記録します。dfsを下げ続け、最終的に臨界点に達したときに条件を判断し、条件に合った記録データと保存データを記録します。満足できない場合は、前のレベルに戻ります。
コード
#include <bits/stdc++.h>
using namespace std;
int n, ans1, ans2[10000][10], arr[10], sum;
void dfs(int stp, int now) {
if (stp > 10) {
if (now == n) {
++ans1;
for (int i = 1; i <= 10; ++i) {
ans2[ans1][i] = arr[i];
}
}
return ;
}
for (int i = 1; i <= 3; ++i) {
if (now + i > n) {
break;
}
arr[stp] = i;
dfs(stp + 1, now + i);
arr[stp] = 0;
}
}
int main() {
cin >> n; dfs(1, 0);
cout << ans1 << '\n';
for (int i = 1; i <= ans1; ++i) {
for (int j = 1; j <= 10; ++j) {
cout << ans2[i][j] << ' ';
}
puts("");
}
return 0;
}
これを学んだ後、みんなが小さなパターンを発見したはずです〜
みんなのための基本的なdfsフレームワークをリストしてください〜
void dfs(目前状态) {
判断边界
尝试每一种可能,跳出可以继续的 {
继续搜索,尝试下一步
}
}
もちろん、列挙はforループを使用する必要はありません!これがみんなの例です、みんなに理解してもらいたいです〜
トピック3:次数nの魔方陣
#include <bits/stdc++.h>
using namespace std;
int n, a[45][45];
void dfs(int x, int y, int now) {
// 目前坐标和下一步要填的数
if (now > n * n) {
// 所有数都填完了,输出,然后结束dfs函数
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= n; ++j) {
cout << a[i][j] << ' ';
}
cout << '\n';
}
return ;
}
// 直接模拟即可
if (x == 1 && y != n) {
a[n][y + 1] = now;
dfs(n, y + 1, now + 1);
} else if (y == n && x != 1) {
a[x - 1][1] = now;
dfs(x - 1, 1, now + 1);
} else if (x == 1 && y == n) {
a[x + 1][y] = now;
dfs(x + 1, y, now + 1);
} else {
if (a[x - 1][y + 1] == 0) {
a[x - 1][y + 1] = now;
dfs(x - 1, y + 1, now + 1);
} else {
a[x + 1][y] = now;
dfs(x + 1, y, now + 1);
}
}
}
int main() {
cin >> n;
a[1][(n + 1) / 2] = 1; // 特殊处理1
dfs(1, (n + 1) / 2, 2);
return 0;
}
これを見て、あなたはすでに基本的なdfsの問題に対処しましたか?役に立ったら、こんにゃくの第2章を楽しみにしていてください!