信息奥赛一本通题解
动态规划
判断整除1195——20200715
思路是递归转递推
- 对数字取模 可以化成 对每个数字取模 这样可以节约数组的空间
- 用二维数组的第二维来记录累加到第i个数的值是否出现
- 数组不能记录负数 所以得对数字进行变化 即 (+k)%k
- 递归转递推
- 递归转递推的公式是 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
- 递归思路是将一个复杂的问题,分为许多子问题去解决的一个方法
- 此题如果正常思路想,所以先进行分析和简化。
- 条件一: 集合里无空集,条件二: 每个子集不相交 ,条件三: 每个子集之和一定能凑成原集合
- 条件分析完了 再把题目翻译一下 有n个蛋糕 放到k个箱子里 每个箱子可以放多个, 但是一定要把箱子放满(蛋糕的数目一定多于箱子的数目),一个箱子能装多个。
- 接下来将大问题分解成小问题,实际上对于每块蛋糕只有两种拿法,要么单独的在一个箱子里,要么和其他人挤在一个箱子里(每个蛋糕必须进箱子 )
- 递归式子s(n,k)的意思是 把n个蛋糕放进k个箱子里
- 第一种情况:蛋糕an单独进箱子, 那么递归式子就是s(n-1,k-1)为什么是k-1 因为我们假设他单独进箱子,这个箱子已经不能有其他小蛋糕了,所以直接减去,也就是说接下来的处理的问题就是s(n-1,k-1)就是把 n-1个蛋糕放到k-1的问题了
- 第二种情况 : 蛋糕an和别人挤在一起,我们先暂且无视这个小蛋糕,我们先安排好其他蛋糕也就是s(n-1,k),最后在来把这个小蛋糕插入到 k个箱子的任意一个里,我们小学2年级就知道,排列组合, 我们将其相乘得到 s(n-1,k)*k
- 方案数直接相加即可得到最后的递归式 即 S(n-1,k-1)+s(n-1,k)*k
- 注意好边界条件即可
下面附上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);
}