P1928 外星密码
对于 100% 的数据:解压后的字符串长度在 20000 以内,最多只有十重压缩。保证只包含数字、大写字母、[
和 ]
。
#include <bits/stdc++.h>
using namespace std;
string fun(){
char ch;//输入字符
int n;//复制次数
string ans="", str="";//str:被复制的字符串
while(cin>>ch){
if(ch=='['){//遇到右括号,寻找被压缩的密码
cin>>n;
str = fun();//递归调用,找到最内层被压缩的密码
while(n--){
ans += str;//复制n次
}
//return ans; 这里return 在本地就不需要加ctrl+Z
// 但提交后一部分会报错(可能有些情况没想到)
}else if(ch==']'){
return ans;//递归出口(一个足矣)
}else ans += ch;
}
}
int main()
{
cout<<fun();
return 0;
}
P1464 Function
因为数据可能很大,单纯递归会 超时(TLE),所以要用 记忆化搜索。我理解的 记忆化 就是,用数组将搜索过程中遇到的每种状态下的答案记在数组中,下次遇到即可直接输出,不必重复搜索。
这道题用 f[a][b][c] :表示 (a,b,c) 状态下的 ans 。
百度“记忆化搜索”
记忆化搜索:算法上依然是搜索的流程,但是搜索到的一些解用动态规划的那种思想和模式作一些保存。
一般说来,动态规划总要遍历所有的状态,而搜索可以排除一些无效状态。
更重要的是搜索还可以剪枝,可能剪去大量不必要的状态,因此在空间开销上往往比动态规划要低很多。
记忆化算法在求解的时候还是按着自顶向下的顺序,但是每求解一个状态,就将它的解保存下来,
以后再次遇到这个状态的时候,就不必重新求解了。
这种方法综合了搜索和动态规划两方面的优点
注意:例如 w(30,−1,0) 又满足条件 1 又满足条件 2,请按照最上面的条件来算.即判断顺序与题目保持一致即可。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll f[25][25][25];
bool vis[25][25][25];
// 把每次0~20以内的答案记录下来,
ll w(ll a,ll b,ll c){
if(a<=0 || b<=0 || c<=0) return 1;
else if(a>20 || b>20 ||c >20) return w(20,20,20);
if(vis[a][b][c]) return f[a][b][c];
else if(a<b && b<c) f[a][b][c] = w(a,b,c-1)+w(a,b-1,c-1)-w(a,b-1,c);
else f[a][b][c] = w(a-1,b,c)+w(a-1,b-1,c)+w(a-1,b,c-1)-w(a-1,b-1,c-1);
vis[a][b][c] = true;
return f[a][b][c];
}
int main()
{
ll a, b, c;
while(cin>>a>>b>>c){
memset(vis,false,sizeof(vis));
//注意:此处不能用连等
if(a==-1 && b==-1 && c==-1) break;
cout<<"w("<<a<<", "<<b<<", "<<c<<") = ";//逗号后和等号前后的空格好坑,建议直接复制题解的进行修改
cout<<w(a, b, c)<<endl;
}
return 0;
}
其他题解:记忆宏(第一次听,所以记下来了)
在我们编写递归函数的时候,经常要用到记忆化搜索。我们可以使用一个宏定义来替换记忆化处理。这样记忆化处理就不会侵染我们的逻辑代码。类似于代理函数的思想。
值得注意的是,宏只需要在递归里替换即可,外部调用不用替换,主要是避免数组下标越界。
#include<bits/stdc++.h>
using namespace std;
// w_men[x][y][z]是否计算过,有直接返回,无计算并"记忆"
#define W_MEM(x,y,z)(w_mem[x][y][z]?w_mem[x][y][z]:w_mem[x][y][z]=w(x,y,z))//记忆宏
int a, b, c ;
int w_mem[25][25][25];
int w(int a, int b, int c) {
if (a <= 0 || b <= 0 || c <= 0)return 1;
if (a > 20 || b > 20 || c > 20)return W_MEM(20, 20, 20);
if (a < b && b < c) return W_MEM(a, b, c - 1) + W_MEM(a, b - 1, c - 1) - W_MEM(a, b - 1, c);
return W_MEM(a - 1, b, c) + W_MEM(a - 1, b - 1, c) + W_MEM(a - 1, b, c - 1) - W_MEM(a - 1, b - 1, c - 1);
}
int main() {
while (1) {
cin >> a >> b >> c;
if (a == -1 && b == -1 && c == -1)break;
cout << "w(" << a << ", " << b << ", " << c << ") = " << w(a, b, c) << endl;
}
}
P1002 [NOIP2002 普及组] 过河卒
A 点的坐标是(0,0)(0,0) ,并且,卒只能向下或向右。
可粗略得出状态转移方程:dp[i][j] = dp[i-1][j] + dp[i][j-1];
也可以是一维数组: dp[j] += dp[j-1]
#include <bits/stdc++.h>
using namespace std;
int xb,yb,mx,my;
long long dp[25][25];
int vis[25][25];
int main()
{
cin>>xb>>yb>>mx>>my;
dp[1][1] = 1;
xb++,yb++,mx++,my++;
//标记马控制的数组
vis[mx][my] = 1;
if(my-1>=0&&mx+2<=xb)vis[mx+2][my-1] = 1;
if(my-2>=0&&mx+1<=xb)vis[mx+1][my-2] = 1;
if(my-2>=0&&mx-1>=0) vis[mx-1][my-2] = 1;
if(my-1>=0&&mx-2>=0) vis[mx-2][my-1] = 1;
if(my+1<=yb&&mx-2>=0)vis[mx-2][my+1] = 1;
if(my+2<=yb&&mx-1>=0)vis[mx-1][my+2] = 1;
if(my+2<=yb&&mx+1<=xb)vis[mx+1][my+2] = 1;
if(my+1<=yb&&mx+2<=xb)vis[mx+2][my+1] = 1;
for(int i=1;i<=xb;i++)
for(int j=1;j<=yb;j++)//0行0列初始化为0,防止dp[i-1][j]或dp[i][j-1]越界出错
if(!vis[i][j]&&(i!=1||j!=1))
dp[i][j] = dp[i-1][j] + dp[i][j-1];
//这里也可压缩为一维数组dp[j]+=dp[j-1];
cout<<dp[xb][yb]<<endl;
return 0;
}
P1044 [NOIP2003 普及组] 栈
#include <bits/stdc++.h>
using namespace std;
int n,f[20];
int main()
{
cin>>n;
f[0] = 1, f[1] = 1;
//由f[2] = 2;-->f[0]=1
for(int i=2;i<=n;i++)//i个数
for(int j=0;j<i;j++)//比f[i]小的数f[j]
f[i] += f[j]*f[i-j-1];
cout<<f[n]<<endl;
return 0;
}
P1028 [NOIP2001 普及组] 数的计算
#include <bits/stdc++.h>
using namespace std;
int n, f[1005];
int main()
{
cin>>n;
for(int i=1;i<=n;i++){//i个数合法数列总数
for(int j=1;j<=i>>1;j++)//保证选择的数<=最后一个的一半
f[i] += f[j];
f[i] ++;
}
cout<<f[n]<<endl;
return 0;
}