洛谷【算法1-4】递推与递归

P1255 数楼梯

对于 100% 的数据,1≤N≤5000。

  • 高精度斐波那契
#include <iostream>
using namespace std;

const int N = 5003;

int len = 1;
int f[N][N];

int main()
{
    int n; scanf("%d", &n);
    
    f[1][1] = 1; f[2][1] = 2;
    
    for (int i = 3; i <= n; i ++ )
    {
        for (int j = 1; j <= len; j ++ )
            f[i][j] = f[i - 1][j] + f[i - 2][j];
        
        for (int j = 1; j <= len; j ++ )
        {
            f[i][j + 1] += f[i][j] / 10;
            f[i][j] %= 10;
            if (f[i][len + 1]) len ++ ;
        }
    }
    
    for (int i = len; i; i -- ) printf("%d", f[n][i]);
}

P1002 [NOIP2002 普及组] 过河卒

对于100% 的数据,1≤n,m≤20,0≤ 马的坐标≤20。

  • 坐标原点从0开始,不方便,因此偏移加一
  • 又发现马涉及到的坐标即使偏移1还是可能越界,因此,偏移2
  • 关于马,只要让它的方案数为0即可,这样即使转移方程中涉及到了马,也只是加了0
#include <iostream>
using namespace std;

const int N = 30;

typedef long long ll;

int dx[8] = {-2, -2, -1, 1, 2, 2, 1, -1};
int dy[8] = {-1, 1, 2, 2, 1, -1, -2, -2};

ll f[N][N];
bool st[N][N];

int main()
{
    ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
    int bx, by, mx, my; cin >> bx >> by >> mx >> my;
    bx += 2, by += 2, mx += 2, my += 2;
    
    st[mx][my] = true;
    for (int i = 0; i < 8; i ++ )
    {
        int x = mx + dx[i], y = my + dy[i];
        st[x][y] = true;
    }
    
    f[2][2] = 1;
    for (int i = 2; i <= bx; i ++ )
        for (int j = 2; j <= by; j ++ )
        {
            if (i == 2 && j == 2) continue;
            if (st[i][j]) continue;
            f[i][j] = f[i - 1][j] + f[i][j - 1];
        }
    
    cout << f[bx][by];
}

P1044 [NOIP2003 普及组] 栈

输入文件只含一个整数 n(1≤n≤18)。

  • 递归 / 记忆化搜索写法
    看数据dfs可能超时,因此想到记忆化搜索
    记忆化搜索,因此,定义一个二维数组f[i][j]表示队列里还有i个数,栈里还有j个数,f[i][j]表示此时的情况数,自然,在f[i][j]有值的情况下就返回
    递归的边界条件,队列中没有数了,就只剩这一种情况了,返回1
    如果栈空,不能弹出栈内元素,只能进栈一次;栈不空,此时可以出栈一次或者进栈一次
#include <iostream>
using namespace std;

typedef long long ll;

const int N = 20;

ll f[N][N];

ll dfs(int que, int stk)
{
    if (f[que][stk]) return f[que][stk];
    if (!que) return 1;
    
    if (stk) f[que][stk] += dfs(que, stk - 1);
    f[que][stk] += dfs(que - 1, stk + 1);
    
    return f[que][stk];
}

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

P1028 [NOIP2001 普及组] 数的计算

一个正整数 n(n≤1000)。

  • 打表方法 :
#include <iostream>
using namespace std;

typedef long long ll;

ll dfs(int u) {
    if (u == 1)
    ll tot = 1;
    for (int i = 1; i <= u / 2; ++ i) {
        tot += dfs(i);
    }
    return tot;
}

int main() {
    cout << "a[1001]={";
    for (int i = 1; i <= 1001; ++ i) {
        cout << dfs(i) << ',';
    }
    cout << "}";
}

f[1] = 1
f[2] = f[1] + 1
f[3] = f[1] + 1
f[4] = f[1] + f[2] + 1
f[5] = f[1] + f[2] + 1
#include <iostream>
using namespace std;

typedef long long ll;

const int N = 1010;

ll f[N];

int main() {
    int n;
    cin >> n;
    
    f[1] = 1;
    
    for (int i = 1; i <= n; ++ i) {
        f[i] = 1;
        for (int j = 1; j <= i / 2; ++ j) {
            f[i] += f[j];
        }
    }
    cout << f[n];
}

P1464 Function

保证输入的数在[−9223372036854775808,9223372036854775807]之间,并且是整数。

  • 用记忆化搜索的时候要注意下标有没有可能为负数
  • 不要写成 return f[a][b][c] = dfs(a - 1, b, c) + ... 的形式,return最后再写
#include <iostream>
using namespace std;

typedef long long ll;

ll f[25][25][25];

ll dfs(ll a, ll b, ll c) {
    if (a <= 0 || b <= 0 || c <= 0) return 1;
    if (f[a][b][c]) return f[a][b][c];
    
    ll t;
    if (a > 20 || b > 20 || c > 20) t = dfs(20, 20, 20);
    else if (a < b && b < c) t = dfs(a, b, c - 1) + dfs(a, b - 1, c - 1) - dfs(a, b - 1, c);
    else t = dfs(a - 1, b, c) + dfs(a - 1, b - 1, c) + dfs(a - 1, b, c - 1) - dfs(a - 1, b - 1, c - 1);
    
    f[a][b][c] = t;
    return f[a][b][c];
}

int main() {
    ll a, b, c;
    
    while (scanf("%lld%lld%lld", &a, &b, &c) == 3) {
        if (a == -1 && b == -1 && c == -1) break;
        
        printf("w(%lld, %lld, %lld) = ", a, b, c);
        
        if (a > 21) a = 21;
        if (b > 21) b = 21;
        if (c > 21) c = 21;
        
        printf("%lld\n", dfs(a, b, c));
    }
}

P1928 外星密码

对于 100%的数据:解压后的字符串长度在 20000 以内,最多只有十重压缩。
保证 有且仅有 数字字母和 [ 和 ]

  • 多重压缩,交给递归
  • 特殊:以下代码我在自己的编译器(Xcode)上是无法运行的,编译器显示函数中必须任何条件都有返回值,而这个代码本身不满足这个条件,由于题目数据保证必然有[和],实际运行没有问题
#include <iostream>
using namespace std;

string dfs() {
    char ch;
    string res = "", s = "";
    while (cin >> ch) {
        if (ch == '[') {
            int k;
            cin >> k;
            s = dfs();
            while (k -- ) {
                res += s;
            }
        } else if (ch == ']') {
            return res;
        } else {
            res += ch;
        }
    }
}

int main() {
    cout << dfs();
}

P2437 蜜蜂路线

对于100%的数据,M,N≤1000

  • 高精度
  • 答案是f[n - m + 1],因为从m走到n相当于从1走到n-m+1
#include <iostream>
using namespace std;

const int N = 1010;

int f[N][N];
int len = 1;

int main() {
    int m, n;
    cin >> m >> n;
    
    f[1][1] = 1;
    f[2][1] = 1;
    
    for (int i = 3; i <= n - m + 1; ++ i) {
        for (int j = 1; j <= len; ++ j) {
            f[i][j] = f[i - 1][j] + f[i - 2][j];
        }
        for (int j = 1; j <= len; ++ j) {
            f[i][j + 1] += f[i][j] / 10;
            f[i][j] %= 10;
        }
        if (f[i][len + 1]) len ++ ;
    }
    
    for (int i = len; i; i -- )
        cout << f[n - m + 1][i];
}

P1164 小A点菜

正数 a i a_i ai可以有相同的数字,每个数字均在1000以内;保证答案的范围在int之内。

  • f[i][j]表示在考虑前i个物品的情况下,价格 恰好 是j的 方案数
  • 转移 :
    1.买当前这个,f[i][j] += f[i - 1][j - a[i]]
    2.不买当前这个,f[i][j] += f[i - 1][j]
    3.其实还有一种情况,就是如果买当前这个,且当前这个的价格恰好等于j,而f[i][0]没有被初始化就是0,实际上应该+1的,因此,我们初始化所有的f[i][0]为1
#include <iostream>
using namespace std;

int f[110][10010];
int a[110];

int main() {
    int n, m;
    cin >> n >> m;
    for (int i = 1; i <= n; ++ i)
        cin >> a[i];
    for (int i = 0; i <= n; ++ i) {
        f[i][0] = 1;
    }
    for (int i = 1; i <= n; ++ i) {
        for (int j = 1; j <= m; ++ j) {
            if (j >= a[i]) f[i][j] += f[i - 1][j - a[i]];
            f[i][j] += f[i - 1][j];
        }
    }
    cout << f[n][m];
}

  • 由于每次f数组的第一维都只用到了i-1,因此可以给f数组降维,不要忘了第二重循环要倒着来
#include <iostream>
using namespace std;

int f[10010];
int a[110];

int main() {
    int n, m;
    cin >> n >> m;
    for (int i = 1; i <= n; ++ i)
        cin >> a[i];
    f[0] = 1;
    for (int i = 1; i <= n; ++ i) {
        for (int j = m; j >= a[i]; -- j)
            f[j] += f[j - a[i]];
    }
    cout << f[m];
}

P1036 [NOIP2002 普及组] 选数

第一行两个空格隔开的整数 n,k(1≤n≤20,k<n)

  • 选择k个数中每个数所填的数
  • (100个数中选50个)去重 -> 不降原则(这道题不会选同一个数组,所以这里的 不降原则不能“平”)
#include <iostream>
#include <cmath>
using namespace std;

bool is_prime(int x) {
    double t = sqrt(x);
    for (int i = 2; i <= t; ++ i) {
        if (x % i == 0)
            return false;
    }
    return true;
}

int a[25];
int n, k, ans;

void dfs(int sel, int sum, int st) {
    if (sel == k + 1) {
        if (is_prime(sum))
            ans ++ ;
        return ;
    }
    for (int i = st; i <= n; ++ i) {
        dfs(sel + 1, sum + a[i], i + 1);
    }
    return ;
}

int main() {
    cin >> n >> k;
    for (int i = 1; i <= n; ++ i)
        cin >> a[i];
    dfs(1, 0, 1);
    cout << ans;
}

P1990 覆盖墙壁

一个整数N(1<=N<=1000000),表示墙壁的长。

  • 取后四位,也就是说%1e4
#include <iostream>
using namespace std;

const int N = 1e6 + 10;
const int mod = 1e4;

int f[N], g[N];

int main() {
    int n;
    cin >> n;
    
    f[0] = 1; f[1] = 1;
    g[0] = 0; g[1] = 1;
    for (int i = 2; i <= n; ++ i) {
        f[i] = ((f[i - 1] + f[i - 2]) % mod + 2 * g[i - 2] % mod) % mod;
        g[i] = f[i - 1] + g[i - 1] % mod;
    }
    cout << f[n];
}

P3612 [USACO17JAN]Secret Cow Code S

该字符串包含最多30个大写字母,并 N ≤ 1 0 18 \leq10^{18} 1018

  • 找到一个类似且更简单的问题角度思考
  • 如果能找到在上一次变形前的位置就可以递归,从而得知该位置的字符
#include <iostream>
using namespace std;
typedef long long ll;

int main() {
    string s; cin >> s;
    ll len = (ll)s.size();
    s = " " + s;
    ll n; cin >> n;
    while (n > len) {
        ll now = len;
        while (now < n) now <<= 1;
        now >>= 1;
        n -= (now + 1);
        if (n == 0) n = now;
    }
    cout << s[n];
}

P1259 黑白棋子的移动

4≤n≤100

  • 题目没有说最小次数
  • 观察样例,发现右边部分总是不动的,看左边有着递归递推的性质,说明能从上一级直接推下一级
  • 每次移动哪两个以及空位在哪里已经确定,因此可以写出函数来移动
#include <iostream>
using namespace std;
const int N = 250;

char g[N];
int n, empty;

void print() {
    for (int i = 1; i <= 2 * n + 2; ++ i) {
        cout << g[i];
    }
    cout << endl;
}
void init() {
    empty = 2 * n + 1;
    for (int i = 1; i <= n; ++ i) {
        g[i] = 'o';
    }
    for (int i = n + 1; i <= 2 * n; ++ i) {
        g[i] = '*';
    }
    for (int i = 2 * n + 1; i <= 2 * n + 2; ++ i) {
        g[i] = '-';
    }
    print();
}
void move(int k) {
    for (int i = 0; i <= 1; ++ i) {
        g[empty + i] = g[k + i];
        g[k + i] = '-';
    }
    empty = k;
    print();
}
void dfs(int u) {
    if (u == 4) {
        move(4); move(8); move(2); move(7); move(1);
    } else {
        move(u); move(2 * u - 1); dfs(u - 1);
    }
}

int main() {
    cin >> n;
    init();
    dfs(n);
}

P1010 [NOIP1998 普及组] 幂次方

1 ≤ n ≤ 2 ∗ 1 0 4 1 \leq n \leq 2*10^{4} 1n2104

  • 每次递归进来找的是 小于等于 u的幂次,因此利用while找到 大于(不是大于等于),然后再退回一步
#include <iostream>
using namespace std;

void dfs(int u) {
    if (u == 0) return ;
    cout << 2;
    int tmp = 1, cnt = 0;
    while (tmp <= u) {
        tmp <<= 1;
        cnt ++ ;
    }
    cnt -- ;
    tmp >>= 1;
    if (cnt == 0 || cnt == 2) {
        cout << "(" << cnt << ")";
    } else if (cnt >= 3) {
        cout << "(";
        dfs(cnt);
        cout << ")";
    }
    u -= tmp;
    if (u) {
        cout << "+";
        dfs(u);
    }
}

int main() {
    int n; cin >> n;
    dfs(n);
}

P1228 地毯填补问题

  • 人为构造,在整张图的中心加一块地毯,使得四小块中每块都有一个障碍,转换成了一个递归问题
#include <iostream>
using namespace std;

void dfs(int sx, int sy, int zx, int zy, int l) {
    if (l == 1) return ;
    l >>= 1;
    if (sx - zx < l && sy - zy < l) {
        printf("%d %d 1\n", zx + l, zy + l);
        dfs(sx, sy, zx, zy, l);
        dfs(zx + l - 1, zy + l, zx, zy + l, l);
        dfs(zx + l, zy + l - 1, zx + l, zy, l);
        dfs(zx + l, zy + l, zx + l, zy + l, l);
    } else if (sx - zx < l && sy - zy >= l) {
        printf("%d %d 2\n", zx + l, zy + l - 1);
        dfs(zx + l - 1, zy + l - 1, zx, zy, l);
        dfs(sx, sy, zx, zy + l, l);
        dfs(zx + l, zy + l - 1, zx + l, zy, l);
        dfs(zx + l, zy + l, zx + l, zy + l, l);
    } else if (sx - zx >= l && sy - zy >= l) {
        printf("%d %d 4\n", zx + l - 1, zy + l - 1);
        dfs(zx + l - 1, zy + l - 1, zx, zy, l);
        dfs(zx + l - 1, zy + l, zx, zy + l, l);
        dfs(zx + l, zy + l - 1, zx + l, zy, l);
        dfs(sx, sy, zx + l, zy + l, l);
    } else {
        printf("%d %d 3\n", zx + l - 1, zy + l);
        dfs(zx + l - 1, zy + l - 1, zx, zy, l);
        dfs(zx + l - 1, zy + l, zx, zy + l, l);
        dfs(sx, sy, zx + l, zy, l);
        dfs(zx + l, zy + l, zx + l, zy + l, l);
    }
}

int main() {
    int k, sx, sy; cin >> k >> sx >> sy;
    dfs(sx, sy, 1, 1, (1 << k));
}

P1498 南蛮图腾

  • 注意每轮中不能一次移动三个,因为在第一个循环中要把原先图案清除
#include <iostream>
#include <cstring>
using namespace std;

const int N = 3000;

char g[N][N];

int main() {
    int n; cin >> n;
    memset(g, ' ', sizeof g);
    g[1][1] = g[1][4] = ' ';
    g[1][2] = g[2][1] = '/';
    g[1][3] = g[2][4] = '\\';
    g[2][2] = g[2][3] = '_';
    int h = 2, w = 4;
    n -- ;
    while (n -- ) {
        for (int i = 1; i <= h; ++ i) {
            for (int j = 1; j <= w; ++ j) {
                g[h + i][j] = g[h + i][j + w] = g[i][j];
                g[i][j] = ' ';
            }
        }
        for (int i = 1; i <= h; ++ i) {
            for (int j = 1; j <= w; ++ j) {
                g[i][j + w / 2] = g[i + h][j];
            }
        }
        h *= 2, w *= 2;
    }
    for (int i = 1; i <= h; ++ i) {
        for (int j = 1; j <= w; ++ j) {
            cout << g[i][j];
        }
        cout << endl;
    }
}

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值