チームトレーニング(3)

チームトレーニング(3)-再帰と再帰(1)

初めは少し嘔吐しました。今週見つけた質問は少し悪かったです。再帰はあまり上手く理解しにくいものでした。その後、いくつかの質問を選んだときに問題がないように感じました。もっと古典的で、投げ込んだだけです。期待していませんでした今のところACがないのは面白いです。さらに、追加の質問も十分に邪悪です。本当に1週間は恥ずかしいです。問題を解決しているとき、今週話しているチームメートは寒いかもしれませんが、週末にいると思っていました。彼は実際にほとんどすべての質問を落としましたA、チームメイトTqlとしか言えません。

再帰の本質:

  • 再帰的な関係を見つける
  • 再帰的な終了記号を探しています
  • 特別検査

質問の例:

ハノイ奇妙1.タワー
タイトル

ハノイの塔の問題の場合、条件は次のとおりです。

1. 4つのタワーA、B、C、Dがあります。

2. n個のディスクがあり、nの数は一定です。

3.各ディスクのサイズは異なります。

4.すべてのディスクは最初にタワーAに積み重ねられ、ディスクのサイズはタワーの上から下に向かって徐々に大きくなります。

5.すべてのディスクをタワーAからタワーDに転送する必要があります。

6.一度に1つのディスクを移動できますタワーが空であるか、一番上のディスクのサイズが移動中のディスクよりも大きい場合、ディスクはこのタワーに移動できます。

すべてのディスクをタワーAからタワーDに移動するために必要な最小移動数を確認してください。

入力フォーマット
入力なし

出力フォーマット
各整数n(1≤n≤12)に対して、条件を満たす最小数の動きを出力し、各結果は1行を占めます。

入力例:入力
なし

出力例:
参照出力形式

分析:この質問は主に3タワーモデルに基づいています。1年目の再帰を見る前に、最初にハノイタワーモデルに問い合わせました。その時のトピックは主に3柱ハノイタワーでしたが、最少運動数を列挙するプロセスは長い間検討されてきたので、別に用意するつもりですが、要するに、三柱ハノイ塔は、[i]が三柱ハノイ塔問題の出発塔であるとして、ルールがありますiの数は、ターゲットタワーに到達するために少なくともi回移動する必要があります。a[1] = 1、a [2] = 3、a [3] = 7、a [4] = 15 ... a [i] = 2 * a [i-1] + 1。次に、4つのタワーの場合、最初にjタワーをトレイBに移動し(列挙に相当)、残りの(i-j)タワーが3タワーの問題になり、(i-j)タワーをトレイDに移動した後次に、ディスクBのjタワーをディスクDに移動し(この場合は4タワーの問題)、1分ほどかかります。しかし、このdpは思いつきませんでしたが、現時点では表面的な理解にすぎません。

ACコード:

#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<stdlib.h>
#include<string.h>
#include<math.h>
using namespace std;
typedef long long LL;
const int maxn = 1e5 + 7;
const int maxm = 2e5 + 7;
const long long inf = 0x3f3f3f3f;
const long long mod = 1e9 + 7;
int n;
int dp[15], f[15];
int main()
{
	dp[1] = 1;
	for(int i = 2; i <= 12; i++)
		dp[i] = dp[i-1] * 2 + 1; //三柱汉诺塔至少移动次数 
	memset(f, 0x3f ,sizeof(f));
	f[1] = 1;
	for(int i = 1; i <= 12; i++)
		for(int j = 1; j < i; j++)
			f[i] = min(f[i], f[j] * 2 + dp[i-j]);
	for(int i = 1; i <= 12; i++)
		printf("%d\n", f[i]);	
	
	return 0;
}


2. Weiminghuトラブルエッジ
トピック

毎年冬に、北京大学威明湖はアイススケートに最適な場所です。北京大学のスポーツチームは多くのスケートを用意しましたが、人が多すぎて、毎日午後に仕事を終えた後、ペアがなくなってしまうことがよくあります。
毎朝、靴のレンタルウィンドウには長い行列ができ、返却する靴がm個、レンタルする靴がn個あると想定しています。今問題は、これらの人々をどのように手配すれば、スポーツグループがスケートを借りることができないという恥ずかしい状況をどのように回避できるかです。(ニーズが同じ2人(たとえば、両方のレンタルシューズまたは両方のリターンシューズ)の交換ポジションは同じ配置です)

入力形式
mとnを表す2つの整数
出力形式
チームの配置数を表す1つの整数。

サンプル入力
3 2
サンプル出力
5

分析:この問題には3つの解決策がありますが、2つあります。結局、dfsも再帰的な考え方です。
1.dfsソリューション:主なアイデアは、返された靴の数、レンタルされた靴の数、残りの靴の数をたどることです。1つ目は、レンタルウィンドウに靴がなく、靴を返品できる人のみである場合と、2つ目は、レンタルウィンドウに靴がレンタル可能な場合です。靴をレンタルする人がn人いて、靴を返却する人がm-1人の場合、靴の有無にかかわらず返却靴を手配できるか、靴を返却する人がmの場合、靴をレンタルする人はn人です-1は、靴を借りる人がまだいるということですが、このとき、窓には靴を借りておく必要があります。次に、少し剪定を行い、より速く実行します。
2.再帰:靴をレンタルした人の数が靴を返却した人の数よりも多く、サイン条件がなく、再帰が終了した場合、後ろから押し進めるか、最後の人が靴をレンタルした人、または靴を返却した人(再帰的な関係)です。靴を借りる人が0の場合、靴を返却する人がいるかどうかが可能性(終了条件)。
3.
dp ソリューション:方程式:* dp [i] [0] = 1;
* dp [i] [j] = dp [i] [j-1] + dp [i-1] [j]; //また靴を借りる人か靴を返す人のどちらかですが、靴を借りる人よりも靴を返す人の方が多いに違いありません

DFSソリューション:

#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<stdlib.h>
#include<string.h>
#include<math.h>
using namespace std;
typedef long long LL;
const int maxn = 1e5 + 7;
const int maxm = 2e5 + 7;
const long long mod = 1e9 + 7;
const long long inf = 0x3f3f3f3f;
int cnt = 0;
int m, n;
void dfs(int rt, int br, int num)//还鞋人数,租鞋人数,剩余鞋数
{
	if((rt == m - 1 && br == n) || (rt == m && br == n-1 && num != 0))
	{
		cnt ++;
		return;
	}
	if(num < 0 || rt > m || br > n) //可行性剪枝 
		return;
	if(num == 0) //当租鞋窗口没有鞋时只能排还鞋的人 
		dfs(rt + 1, br, num + 1);
	if(num > 0)
	{
		dfs(rt + 1, br, num + 1); //排还鞋的人
		dfs(rt, br + 1, num - 1); //排借鞋的人 
	}
}
int main()
{
	scanf("%d%d", &m, &n); 
	dfs(0, 0, 0); //还鞋人数,租鞋人数,剩余鞋数
	printf("%d", cnt); 
	return 0;
}

再帰的な解決策:

int f(int m, int n)
{
    if (m < n) //当队伍里还鞋人数m小于租鞋人数n时,无论如何摆放,都不合法
        return 0;
 	else if (n == 0) //当队伍里的租鞋人数为0时如果还有还鞋的,都只有一种排法 
        return 1;
    else 
        return f(m - 1, n) + f(m, n - 1);
}

dpソリューション:

#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<stdlib.h>
#include<string.h>
#include<math.h>
using namespace std;
typedef long long LL;
const int maxn = 1e5 + 7;
const int maxm = 2e5 + 7;
const long long inf = 0x3f3f3f3f;
const long long mod = 1e9 + 7;
int m, n;
int dp[20][20];
int main()
{
	scanf("%d%d", &m, &n);
	for(int i = 1; i <= m; i++)
		dp[i][0] = 1; // 只要租鞋的人为0,还鞋的人 >= 1,方案数均为一种. 
	for(int i = 1; i <= m; i++)
		for(int j = 1; j <= i; j++) //j <= i 控制租鞋的人不能大于还鞋的人,一旦大于,排队方案则为0 
			dp[i][j] = dp[i][j-1] + dp[i-1][j];
	printf("%d", dp[m][n]);
	return 0;
} 

3.アップルサルポイントの
タイトル

  秋になると、n匹のサルがたくさんのリンゴを拾って洞窟に入れ、翌日には割ることに同意しました。これらのサルは、モンキーキングサンウコンを崇拝しているので、みんな彼にリンゴを残したいと思っています。最初のサルは静かに洞窟に来て、リンゴをn個の等しい部分に分け、残りのm個のリンゴを食べ、次に1つの部分を隠し、最後に残りのリンゴを元に戻しました。これらのサルは順番に静かに洞窟にやって来て、同じ操作を行いました。翌日、これらのサルは洞窟にやって来て、残りのリンゴをn個のポイントに分けましたが、偶然にもまだm個残っていました。これらのサルが少なくともいくつのリンゴを選んだか尋ねてください。
入力形式
  2つの整数、nm
出力形式
  元のリンゴの数を示す整数
サンプル入力
5 1
サンプル出力
15621
データサイズと規則
0 <m <n <9

分析:この質問は、最初は再帰方程式を導入しましたが、巧妙に使用されることはありませんでした。その後、私は不滅のものを調べて、ようやく肯定的な解決策を見つけました。まず、アムウェイは興味深い数学の問題を説明しました:サルは桃を分割します。これは、Li Zhengdaoが当時中国科学技術大学のジュニアクラスに与えた数学の問題です。この質問はこのように拡張できますが、理解する方法がわかりませんだから私の思考のトレインに従ってください。
まず、mを食べて自分の部分を隠した後、(i + 1)番目のサルを推測できます(f(i)-m)*((n-1)/ n)、これは配信ですプッシュはf(i + 1)=(f(i)-m)*((n-1)/ n)であり、次にf(i)=(f(i + 1)* n /( n-1))+ m。その後、終了条件について考えていて、行き詰まっていました。最初はf(n)%n = f(n + 1)= mだと思っていましたが、うまくいかないことがわかりました。最終的な解決策は、賢い解決策を使用することでした。列挙すると、まず、n番目のサルがmを食べ終えて自分の部分を隠すと、少なくともn + mが残り、翌日、n部分に分割すると、mが残ります(条件が満たされます)が、このように、以前のf(x)には整数の解がない場合があるため、n番目のサルがmを食べて自分の部分を非表示にすると、最初のサルまで(kn + m)が残る可能性があることを列挙できます。 2回目に解かれるf(x)の整数解のみが必要です。

ACコード:

#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<stdlib.h>
#include<string.h>
#include<math.h>
using namespace std;
typedef long long LL;
const int maxn = 1e5 + 7;
const int maxm = 2e5 + 7;
const long long inf = 0x3f3f3f3f;
const long long mod = 1e9 + 7;
int f[15];
int n, m;
int main()
{
	scanf("%d%d", &n, &m);
	f[n] = m + n; //当第n个猴子拿完他的苹果至少有m + n个去给第二天分 
	for(int i = n - 1; i >= 0; i--)
	{	
		f[i] =	(f[i+1] * n / (n - 1)) + m;
		if( ( (f[i] - m) * (n - 1) / n) != f[i+1]) //if(f[i+1] * n % (n-1) != 0)
		{
			f[n] += n;
			i = n;
		}
	}
	printf("%d", f[0]);
	return 0;
} 

4.タイルの配置
タイトル
2つの異なるタイルが与えられた場合、長さN(1 <= N <= 10)の床があります。1つは長さ1、もう1つは長さ2で、数は無制限です。長さNのこの床をカバーするには、いくつの異なる舗装方法が必要ですか?
  たとえば、長さが4の地面には、次の5種類の舗装方法があります
  。4= 1 + 1 + 1 + 1 + 1
  4 = 2 + 1 + 1
  4 = 1 + 2 + 1
  4 = 1 + 1 + 2
  4 = 2 + 2
  プログラミングは再帰的な方法を使用して上記の問題を解決します。
入力形式
  床の長さを表すNは1つだけあります
出力形式
  すべての異なるタイル敷設方法の総数を表す数値を出力します
サンプル入力
4
サンプル出力
5

分析:
再帰的解決策:この問題は、階段を上る問題に取って代わります。階段を上るのは、一度に1つのグリッドか、一度に2つのグリッドです。2つのオプションがあります。グリッドが1つだけ残っている場合、メソッドは1つだけです。 2つのグリッドには2つの方法があります。ここで、残りの2つのグリッドには終了条件が必要であることに注意することが重要です。noと仮定すると、f(2-1)+ f(2-2)スキームが表示されます。f(0)終了条件がない場合は、無限にループし続けますが、階段の数が0の場合、計画番号1を追加できます。これは、所定の位置に移動していません。これは、より適切です。これは、タイルを配置することと同じです。
dpソリューション:
方程式:* dp [1] = 1;
* dp [2] = 2;
* dp [i] = dp [i-1] + dp [i-2];

再帰的な解決策:

#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<stdlib.h>
#include<string.h>
#include<math.h>
using namespace std;
typedef long long LL;
const int maxn = 1e5 + 7;
const int maxm = 2e5 + 7;
const long long inf = 0x3f3f3f3f;
const long long mod = 1e9 + 7;
int f(int n)
{
	if(n < 0)
		return 0;
	if(n <= 2)
		return n;
	return f(n - 1) + f(n - 2); //选择走一阶/走两阶 
}


int main()
{
	int n;
	scanf("%d", &n);
	int cnt = f(n);
	printf("%d", cnt);
	return 0;
}

dpソリューション:

#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<stdlib.h>
#include<string.h>
#include<math.h>
using namespace std;
typedef long long LL;
const int maxn = 1e5 + 7;
const int maxm = 2e5 + 7;
const long long inf = 0x3f3f3f3f;
const long long mod = 1e9 + 7;
int main()
{
	int n, dp[35];
	scanf("%d", &n);
	dp[1] = 1, dp[2] = 2;
	for(int i = 3; i <= n; i++)
		dp[i] = dp[i-1] + dp[i-2];
	printf("%d", dp[n]);
	return 0;
}

再帰的な列挙型の配列
話題

n個の整数1〜nを一列に並べ、ランダムに順序を入れ替えて、すべての可能な順序を出力します。

入力フォーマット
整数n。

出力形式
すべてのプランを1行に1つずつ、昇順に出力します

まず、同じ行にある2つの隣接する数値がスペースで区切られます。

次に、2つの異なる行について、対応する添え字の数が1つずつ比較され、辞書式順序が最初にランク付けされます。

データ範囲
1≤n≤9

入力例:
3

出力例:
1 2 3
1 3 2
2 1 3
2 3 1
3 1 2
3 2 1

分析:全体的なアイデアはdfsであり、これは主に渡された数値をマークしてからバックトラックを実行します。カットオフ条件は、ステップn + 1に進むことです。

ACコード:

#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<stdlib.h>
#include<string.h>
#include<math.h>
using namespace std;
typedef long long LL;
const int maxn = 1e5 + 7;
const int maxm = 2e5 + 7;
const long long inf = 0x3f3f3f3f;
const long long mod = 1e9 + 7;
int n;
int vis[15], val[15];
void dfs(int step)
{
	if(step == n + 1) //枚举到最后一步 (接下来无数可枚举)
	{
		for(int i = 1; i <= n; i++)
			printf("%d%c", val[i], i == n ?'\n':' ');
		return;
	} 
	for(int i = 1; i <= n; i++)
	{
		if(!vis[i]) //判断之前是否枚举过
		{
			vis[i] = 1; //标记已枚举过i 
			val[step] = i; //记录第x步的数
			dfs(step + 1); //向下搜索
			//回溯
			vis[i] = 0;
			val[step] = 0; 
		} 
	}
}

int main()
{
	scanf("%d", &n);
	dfs(1);
	return 0;
}

数の6計算
トピックス

次のプロパティ番号を持つ数値(入力自然数nを含む)を見つける必要があります。

最初に自然数n(n≤500)を入力し、次にこの自然数を次のように処理します。

1.治療なし。

2.自然数をその左側に追加しますが、自然数は元の数の半分を超えることはできません。

3.数値を追加した後、自然数を追加できなくなるまで、このルールに従って処理を続行します
入力
1自然数n(n≤1000)
出力
1この整数は、このプロパティで数値の数を示します。
サンプル入力
6
サンプル出力
6

備考:
条件を満たす数は
6、16、26、126、36、136

分析:
解決策1:メモリプルーニング+
f_remember配列を再帰的に開いて、以前に満たされているf [n]に対していくつかのメモリ最適化を実行します。基本的な考え方は、数値を半分にしてから1-n / 2から除算することです生成された修飾数が累積され、最後にサンプル6などが追加されます。実際には6/2 = 3、1〜3トラバースです。1〜3は6の左側に追加されるため、 16、26、36、2 / 2 = 1、2の左側に1つの一致が追加され、126が生成され、次に3/2 = 1、136が生成されます。 。
解決策2:再帰
f [1] = f [0] + 1;
f [2] = f [1] + 1;
f [3] = f [1] + 1;
f [4] = f [2] + f [1] + 1;
f [5] = f [2] + f [1] + 1;
f [6] = f [3] + f [2] + f [1] + 1;
ソリューション3: dp
は、f [i]を使用して最初のi番号で共有されるスキームの数を表し、方程式は次のようになります。f[i] = f [i-1] + f [i / 2] + 1、前者(i-1)プログラムの総数+ i / 2の前のプログラムの数とそれ自体、実際、i / 2の前のプログラムの数は、i番目の数に必要な条件です。

再帰的な解決策:

#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<stdlib.h>
#include<string.h>
#include<math.h>
using namespace std;
typedef long long LL;
const int maxn = 1e5 + 7;
const int maxm = 2e5 + 7;
const long long inf = 0x3f3f3f3f;
const long long mod = 1e9 + 7;
int f_remember[1005];
int f(int n)
{
	int cnt = 0;
	if(n == 1)
		return 1;
	if(f_remember[n])
		return f_remember[n];
	for(int i = 1; i <= n / 2; i++)
		cnt += f(i);
	return f_remember[n] = cnt + 1; //还要加上自己本身 
}

int main()
{
	int n, ans = 0;
	scanf("%d", &n);
	ans = f(n);
	printf("%d", ans);
	return 0;
}

再帰的な解決策:

#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<stdlib.h>
#include<string.h>
#include<math.h>
using namespace std;
typedef long long LL;
const int maxn = 1e5 + 7;
const int maxm = 2e5 + 7;
const long long inf = 0x3f3f3f3f;
const long long mod = 1e9 + 7;
int f[1005];

int main()
{
	int n;
	memset(f, 0, sizeof(f));
	scanf("%d", &n);
	for(int i = 0; i <= n; i++)
	{
		for(int j = 1; j <= i / 2; j++)
		{
			f[i] += f[j];
		}
		f[i] ++; //加上本身 
	}
	printf("%d", f[n]);
	return 0;
}

dpソリューション:

#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<stdlib.h>
#include<string.h>
#include<math.h>
using namespace std;
typedef long long LL;
const int maxn = 1e5 + 7;
const int maxm = 2e5 + 7;
const long long inf = 0x3f3f3f3f;
const long long mod = 1e9 + 7;
int f[1005]; //f[i]代表0 - i 共有几种解法 
int main()
{
    int n;
    scanf("%d", &n);
   	f[0] = 1, f[1] = 1;
    for(int i = 2; i <= n; i++)
        f[i] = f[i-1] + f[i/2] + 1;
    printf("%d", f[n] - f[n-1]);
}

おすすめ

転載: www.cnblogs.com/K2MnO4/p/12688912.html