信息奥赛一本通题解

动态规划

判断整除1195——20200715

在这里插入图片描述

思路是递归转递推

  1. 对数字取模 可以化成 对每个数字取模 这样可以节约数组的空间
  2. 用二维数组的第二维来记录累加到第i个数的值是否出现
  3. 数组不能记录负数 所以得对数字进行变化 即 (+k)%k
  4. 递归转递推
  5. 递归转递推的公式是 ans[i][j] = ans[i - 1][((j - r[i])+k)%k] || ans[i - 1][((j + r[i ])+ k) % k]
#include<iostream>
using namespace std;
int ans[10005][201];
int r[10005];
int main()
{
	int n, k;
	cin >> n >> k;
	for (int i = 1; i <= n; i++)
	{
		cin >> r[i];
	}
	ans[1][(0+r[1]%k+k)%k] = 1;//因为r[1]也有可能是负数 所以需要+k %k 
	// 所以总结出 对负数控制为整数的余数的方法是+ k % k
	for (int i = 2; i <= n; i++) //从2开始即可 1 的可能已经列出 
	{
		for (int j = 0; j < k; j++)//只需要遍历到k即可
		{
			ans[i][j] = ans[i - 1][((j - r[i])+k)%k] || ans[i - 1][((j + r[i ])+ k) % k];
			// 用| 来表示点亮的关系 ans[i][numb] numb表示第i个数字 且到目前的总和的数字ans 是否被点亮
			// 由于第二个下标会出现负数 所以要用 %k +k %k 的方法来控制 让其变为正数 
			// 打个比方 对于第二个数字 2  ans[2][j] 
			// j 是从0 到k 的数字 循环跑一下 看看能不能由第一个数字的加or减得到 
			// 如果有其中一个 那么 ans[2][j] 就表明 能够被表示出来 所以数字被点亮 
		}
	}
	if (ans[n][0])// 第n个数字处理完后 0这个数字有没有被点亮 
	{
		cout << "YES";
	}
	else
	{
		cout << "NO";	
	}
}

踩方格1196——20200715

在这里插入图片描述
分析:

1、首先判断每次操作的情况,可以向右走,可以向前走,也可以向左走,由于左右两边等价,所以左右走统称侧走。
2、 每一步的方案数字最根本是由上一步的方案数衍生出来,我们首先设f(n),其含义为允许走n步,共有n种不同的路线。
3.每步其实只有两种情况,侧面和前面,左或右面衍生出两种不同的情况,向前可以衍生三种不同的情况,所以我们可以初步得到公式 f(n)=[n-1的侧面条数] * 2 +[n-1正面条数 ] * 3
4. 再次简化公式 [n-1]的正面条数 是由n-2所有方案数向上衍生出 所以条数就是 f(n-2) * 1
5. 而[n-1的侧面条数] == f(n-1)-f(n-2)
6. 代入公式 f(n)= ( f(n-1)-f(n-2) )* 2 + f(n-2) * 3 化简可得 f(n)=2*f(n-1)+f(n-2) 便是递推式 循环即可得到结果
7. 注意初始状态 f(0) = 1

下面附上ac代码

#include<iostream>
using namespace std;
long long  f[10005];
int main()
{
	int n, k;
	cin >> n;
	f[0] = 1;
	f[1] = 3;
	for (int i = 2; i <= n; i++)
	{
		f[i] =f[i - 1] * 2+ f[i-2] ;
	}
	cout << f[n];

}

递归问题

汉诺塔递归求解20200716

问题:汉诺塔

1.汉诺塔递归求解的方法主要将每个小步骤看成一个大步骤,让系统去解决繁琐的问题
2.可以讲其大体的分为3步 1. 把n-1 的柱子丢中转 2.第n个柱子 丢到目标去 3.最后把放在中转的n-1个柱子丢到目标柱去
3.处理完以后 …一切重新开始 只不过少了一个

下面附上代码

#include<iostream>
#include <stdio.h>
using namespace std;
long long  f[10005];
int ans;	
void s(int n, char star, char mid, char aim)
{
	if (n == 0) return;//终止条件

	s(n-1, star, aim, mid);//把n-1移动到中间

	ans++;//把第n个柱子丢到目标柱
	printf("%c->%d->%c\n", star, n, aim);
	//把n-1从中转柱移动到目标柱
	s(n - 1, mid, star, aim);
}
int main()
{
	int q;	
	char a, b, c;
	cin >> q >> a >> b >> c;
	s(q, a, c, b);
}

集合的划分20200716

在这里插入图片描述
在这里插入图片描述

  1. 递归思路是将一个复杂的问题,分为许多子问题去解决的一个方法
  2. 此题如果正常思路想,所以先进行分析和简化。
  3. 条件一: 集合里无空集,条件二: 每个子集不相交 ,条件三: 每个子集之和一定能凑成原集合
  4. 条件分析完了 再把题目翻译一下 有n个蛋糕 放到k个箱子里 每个箱子可以放多个, 但是一定要把箱子放满(蛋糕的数目一定多于箱子的数目),一个箱子能装多个。
  5. 接下来将大问题分解成小问题,实际上对于每块蛋糕只有两种拿法,要么单独的在一个箱子里,要么和其他人挤在一个箱子里(每个蛋糕必须进箱子 )
  6. 递归式子s(n,k)的意思是 把n个蛋糕放进k个箱子里
  7. 第一种情况:蛋糕an单独进箱子, 那么递归式子就是s(n-1,k-1)为什么是k-1 因为我们假设他单独进箱子,这个箱子已经不能有其他小蛋糕了,所以直接减去,也就是说接下来的处理的问题就是s(n-1,k-1)就是把 n-1个蛋糕放到k-1的问题了
  8. 第二种情况 : 蛋糕an和别人挤在一起,我们先暂且无视这个小蛋糕,我们先安排好其他蛋糕也就是s(n-1,k),最后在来把这个小蛋糕插入到 k个箱子的任意一个里,我们小学2年级就知道,排列组合, 我们将其相乘得到 s(n-1,k)*k
  9. 方案数直接相加即可得到最后的递归式 即 S(n-1,k-1)+s(n-1,k)*k
  10. 注意好边界条件即可

下面附上ac代码

#include<iostream>
#include <stdio.h>
using namespace std;
long long  s(int n , int k	)// n个元素划分为 k个不相交的子集
{
	//1.成为一个单独的子集 

	if (n == k || k == 1)
	{
		return 1;
	}
	else if (n == 0 || k == 0)
	{
		return 0;
	}
	else
	{
	return s(n - 1, k - 1) + k * s(n - 1, k);
	}
}
int main()
{
	int n, k;
	cin >> n >> k;
	cout << s(n, k);
}
	

逆波兰表达式 20200717

在这里插入图片描述

1.首先观察式子 * + A B C 数字有小数 而且 有各种符号 还有空格, 绝对不能把一串输入整合成一个长字符串, 所以初步思路是 边操作(递归),边输入。
2.再观察 如果输入的是 (+ A B ) 也就是说这三个字符 最终的返回值是 A +B之合的浮点数
3.刚开始可能没思路,但是不急慢慢看, 我们假设,遇到符号的时候 ,后面两位的输入,直接让其做符号运算,那如果 后面一位 还是符号呢? 比如说: * + 1 2 3 是不是转为 * 3 3 也就是说,如果遇到符号位了,后面两位的输入如果有符号 那么是不是要先处理后面的?再假设,如果非常多非常多的符号,是不是数字运算一直要延推到后面? 这种种种套娃的恶心操作,所以就得到我们初步的处理思路——递归
4.对于比如 + - * A B C D 是不是 (A* B - C )+ D 我们先看最外层 左边的 (A* B - C ) 和右边的D 相加,对于最开始遇到加号而言,是不是仅仅是 x+ y, 随后我们输入了x,但是x是一个减号,那是不是又会继续递归 变成 q-w ,我们再次先处理q,然后我们又是输入了一个符号 ——乘号,我们就优先处理 r*t 返回到 q那里去。
5. 根据分析,我们采用边递归边输入的方式来处理这串数字 详情看代码

附上ac代码

#include<iostream>
#include<algorithm> 
#include<string>
#include<cstdio>
using namespace std;
char str[100];
double hxs;
double exp()
{
	cin >> str;
	switch(str[0])
	{
		//一边输入一边递归
		//如果遇到符号的就进行运算, 如果是非符号的就继续递归
		//递归输入法之一边递归之一边求解问题
	case '+':return  exp() + exp(); break;
	case '-':return  exp() - exp(); break;
	case '*':return  exp() * exp(); break;
	case '/':return  exp() / exp(); break;
		//如果是数字的情况 那就返回数字的浮点型
	default: return atof(str);// atof 字符串转浮点数
	}
}
// 递归是引发思路的一个 开关
//  * + 2 3 4 
int main()	
{
	printf("%lf\n", exp());
}
	

分解因数20200717

在这里插入图片描述
ac代码

思路1:对于一个数 我们一直拿数字去除他 从 如果最后能除尽,说明这些数字是他的因数

#include<iostream>
using namespace std;
double  ans;
void f(int t,int s )// 
{
	if (t == 1)// 最后一定会是1 函数的终止条件  每个可能最后都会来到这里 恰好能够用来计数
	{
		ans++;
		return;
	}
	else
	{
		for (int i = s; i <=t; i++)// 从约数开始就不会重复
		{
			if (t % i == 0)
			{	
				 f(t / i,i);
			}		
		}
	}
}
int main()
{
	int n;
	cin >> n;
	for (int i = 1; i <= n; i++)
	{
		int t;
		ans = 0;
		cin >> t;
		f(t,2);
		cout << ans<<endl;
	}
}

思路2 :比较简单的发现 递减递归更加实用 详细看代码注释
如果能整除 那么面临两种情况
1 是除掉 2 是除数减一
所以我们要将其相加 就能返回所有的情况

#include<iostream>
using namespace std;

int f(int n ,int p	)
{
	if (p == 1)//避免重复
	{
		return 0;
	}
	if (n == 1)//成功除到到底了
	{
		return 1;
	}
	if (n % p == 0)
	{
		//如果能整除 那么面临两种情况 
		//1 是除掉 2 是除数减一
		//要将除数相加 返回所有的情况 
		return f(n / p, p)+f(n,p-1);
	}
	return f(n, p - 1); // 如果不能整除
}		
int main()
{
	int t;
	int n, p;
	cin >> t;
	while (t--)
	{
		cin >> n;
		cout << f(n, n) << endl;
	}
}

2的幂次方表示20200720

在这里插入图片描述

分析:用递归解决
1.对于一个数字,假设9,可以拆分成2^3 + 2^0 所以我们先找到 比这个数字9最小的2的幂次方
2.其次对于2的三次方 ,3 这个数字也能进行拆分,那么我们就直接递归下去,直到不能拆分位置
3.最后的时候判断一下有无余项,没有余项就直接结束好了,有余项就继续递归拆分即可

ac代码如下:

#include<iostream>
#include<cstdio>
typedef long long ll;
int a[100];
using namespace std;
void qwq(int x)//拆分的数
{
	int i = 16;// i是幂 
	if (x == 0)//x是 0 直接输出
	{
		printf("0");
		return;
	}
	else if (x == 2)
	{
		printf("2");
		return;
	}
	while (a[i] > x)
	{
		i--;
	}
	if (i == 1)
	{
		printf("2");
	}
	else
	{
		printf("2(");
		qwq(i);//继续指拆分
		printf(")");
	}

	if (x - a[i] != 0)//检查有无余项
	{
		printf("+");
		qwq(x - a[i]);//继续递归拆分
	}
}
int main()
{
	//拆分二的指数幂
	//打表
	a[0] = 1;
	int n;
	cin >> n;
	for (int i = 1; i <= 16; i++)
	{
		a[i] = a[i - 1] * 2;
	}
		qwq(n);
	
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值