递推
先放出两道题
翻硬币
小明正在玩一个“翻硬币”的游戏。
桌上放着排成一排的若干硬币。我们用 * 表示正面,用 o 表示反面(是小写字母,不是零)。
比如,可能情形是:oo*oooo
如果同时翻转左边的两个硬币,则变为:oooo***oooo
现在小明的问题是:如果已知了初始状态和要达到的目标状态,每次只能同时翻转相邻的两个硬币,那么对特定的局面,最少要翻动多少次呢?
我们约定:把翻动相邻的两个硬币叫做一步操作。
输入格式
两行等长的字符串,分别表示初始状态和要达到的目标状态。
输出格式
一个整数,表示最小操作步数
数据范围
输入字符串的长度均不超过100。
数据保证答案一定有解。
输入样例1:
oo
输出样例1:
5
输入样例2:
ooo***
ooo***
输出样例2:
1
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 103;
string s1,s2;
int main()
{
cin>>s1>>s2;
int res = 0;
for(int i = 0 , j = 0;i<s1.size() - 1;i++,j++)
{
if(s1[i] != s2[j])
{
res++;
if(s1[i] == '*')s1[i] = 'o';
else s1[i]== '*';
if(s1[i+1] == '*')s1[i+1] = 'o';
else s1[i+1] = '*';
}
else continue;
}
cout<<res;
return 0;
}
费解的开关
你玩过“拉灯”游戏吗?
25 盏灯排成一个 5×5 的方形。
每一个灯都有一个开关,游戏者可以改变它的状态。
每一步,游戏者可以改变某一个灯的状态。
游戏者改变一个灯的状态会产生连锁反应:和这个灯上下左右相邻的灯也要相应地改变其状态。
我们用数字 1 表示一盏开着的灯,用数字 0 表示关着的灯。
下面这种状态
10111
01101
10111
10000
11011
在改变了最左上角的灯的状态后将变成:
01111
11101
10111
10000
11011
再改变它正中间的灯后状态将变成:
01111
11001
11001
10100
11011
给定一些游戏的初始状态,编写程序判断游戏者是否可能在 6 步以内使所有的灯都变亮。
输入格式
第一行输入正整数 n,代表数据中共有 n 个待解决的游戏初始状态。
以下若干行数据分为 n 组,每组数据有 5 行,每行 5 个字符。
每组数据描述了一个游戏的初始状态。
各组数据间用一个空行分隔。
输出格式
一共输出 n 行数据,每行有一个小于等于 6 的整数,它表示对于输入数据中对应的游戏状态最少需要几步才能使所有灯变亮。
对于某一个游戏初始状态,若 6 步以内无法使所有灯变亮,则输出 −1。
数据范围
0<n≤500
输入样例:
3
00111
01011
10001
11010
11100
11101
11101
11110
11111
11111
01111
11111
11111
11111
11111
输出样例:
3
2
-1
#include<iostream>
#include<cstring>
#include <algorithm>
using namespace std;
const int N =6;
int n;
char g[N][N],b[N][N];
int dx[5] = {-1, 0, 1, 0,0}, dy[5] = {0, 1, 0, -1,0};
void make(int x,int y)
{
for(int i=0;i<5;i++)
{
int xx = x + dx[i],yy = y + dy[i];
if(xx>4||xx<0||yy>4||yy<0)continue;
g[xx][yy]^=1;
}
}
int main()
{
int T;
cin>>T;
while(T--)
{
for(int i=0;i<5;i++)
cin>>g[i];
int res=10;
for (int op = 0; op < 32; op ++ )
{
memcpy(b, g, sizeof g);
int step = 0;
for (int i = 0; i < 5; i ++ )
if (op >> i & 1)
{
step ++ ;
make(0, i);
}
for (int i = 0; i < 4; i ++ )
for (int j = 0; j < 5; j ++ )
if (g[i][j] == '0')
{
step ++ ;
make(i + 1, j);
}
bool dark = false;
for (int i = 0; i < 5; i ++ )
if (g[4][i] == '0')
{
dark = true;
break;
}
if (!dark) res = min(res, step);
memcpy(g,b,sizeof b);
}
if(res > 6)res =-1;
cout<<res<<endl;
}
return 0;
}
这两道题在思路上有很多相同点,同时在形式上有点不同,费解的开关题确定了第一排的状态后,从第二排开始依次按照上一排的状态进行改变,就像薛定谔的猫,这只猫有着生和死两种状态,当你去观测(确定)这种状态时,如果生:主人会高兴地去撸猫,如果死:主人就会伤心,而高兴和伤心就是由前面这个状态决定的。因此,就没必要枚举主人到底高不高兴。并且,一个开关按两次等于原来,即周期。
或者用反证法证明:假设确定前一排的状态后,当前一排的状态不是唯一确定的;
由题意可知,当前一格的状态可以由上一个,左一个,右一个,下一个确定,又因为在第二排开始上一个已经确定,左一个一直取决于左一个,直到最左时,左边已经没有可以确定当前格子的了,右边同理。
那么就剩下下面的格子确定,才能确定当前格子。
因此倒数第二排由第一排和最后一排确定,但我们已知题目所给最后一排的最终状态全是1,那么中间所有层状态就已知,所以这就与当前一排状态不是唯一确定矛盾,结论得证。
而第二题,只有一维,那么想从第一格开始,到倒数第二格,都具有变和不变两种状态(和从最后一个到第二格同理,因为倒数第二格数两个数包含最后一个,最后一个数往前数一个也是一样的区间,因此没有区别),证明思路一样,只是这发生在一维,保证有唯一解,就不需要判断最后一个了。
因此懂得证明思路,递推也就不难理解了,斐波那契数列也就是当前数取决于前两个数,因此,确定第一第二个数,后面所有数字均确定。
因此递推公式的推导和计算才是核心,很像动态规划的刷表和填表。而递归的思路则是直接考虑想要什么,再设立终止状态达成中间过程。
递归
先放个几道题
递归实现指数型枚举
从 1∼n 这 n 个整数中随机选取任意多个,输出所有可能的选择方案。
输入格式
输入一个整数 n。
输出格式
每行输出一种方案。
同一行内的数必须升序排列,相邻两个数用恰好 1 个空格隔开。
对于没有选任何数的方案,输出空行。
本题有自定义校验器(SPJ),各行(不同方案)之间的顺序任意。
数据范围
1≤n≤15
输入样例:
3
输出样例:
3
2
2 3
1
1 3
1 2
1 2 3
#include <iostream>
using namespace std;
int n;
int state;
void dfs(int u,int state)
{
if(u==n) //终止状态
{
for(int i=0;i<n;i++)
{
if(state>>i&1)cout<<i+1<<" ";
}
cout<<endl;
return ;
}
dfs(u+1,state);
dfs(u+1,state| 1<<u);
}
int main()
{
cin>>n;
dfs(0,0);
return 0;
}
这道题的其实也可以看成是状态选择,将3 看成 0 0 1 即选3,1,2不选。2 3看成0 1 1 ,即2,3 选 ,1不选。
那么,递归还有个思路就是从决策树的角度看,
(图片摘自柒月栗子)
(图片摘自柒月栗子)
(图片摘自柒月栗子)
(图片摘自柒月栗子)
(虽然我画了,但太丑【划掉】)
实现排列型枚举
把 1∼n 这 n 个整数排成一行后随机打乱顺序,输出所有可能的次序。
输入格式
一个整数 n。
输出格式
按照从小到大的顺序输出所有方案,每行 1 个。
首先,同一行相邻两个数用一个空格隔开。
其次,对于两个不同的行,对应下标的数一一比较,字典序较小的排在前面。
数据范围
1≤n≤9
输入样例:
3
输出样例:
1 2 3
1 3 2
2 1 3
2 3 1
3 1 2
3 2 1
#include <iostream>
using namespace std;
int n;
int st[10];
bool used[10];
void dfs(int cur)
{
if (cur == n)
{
for(int i=0;i<n;i++)cout<<st[i]<<" ";
cout<<endl;
return;
}
for(int i=1;i<=n;i++)
{
if (!used[i])
{
used[i]=true;
st[cur]=i;
dfs(cur+1);
used[i]=false;
}
}
}
int main()
{
cin>>n;
dfs(0);
return 0;
}
排列组合的理解在于都处于选择的状态,但问题是输出顺序的问题,怎么不重复不遗漏每一种情况,上面一题不考虑顺序,那么在递归过程中体现是否考虑过三种可能就行。而这一题,因为都要选择,那么从前往后考虑,属于每一个位置放谁的问题。
拿1 2 3 举例,现在枚举第一个位置,放1可以,2也可以,3也可以,如果放了1,第二个位置只能放2,3。以此类推……