递推6题【好玩的数列/NOIP数的计算/打鼹鼠/游戏/数的作业/最长上升子序列】

难度:★☆☆☆☆

NC21169-1005好玩的数列

斐波那契数列。可以用递推公式直接做,也可以递归(记忆化dfs)

★★☆☆☆

NC16690-[NOIP2001]数的计算

递推


# include <iostream>
using namespace std;
long long f[1010]={0};
int main(void)
{
    int n; cin >> n;
    f[0] = f[1] = 1;
    for (int i=2; i<=n; i++)
        for (int j=0; j*2<=i; j++) f[i]+=f[j];

    cout << f[n];
    return 0;
}

★★★☆☆

NC20035-[HNOI2004]打鼹鼠

想法一:

免费馅饼(数塔)是一维的,此题是二维的

dp[k][i][j] 表示第k秒机器人出现在(i, j)的最多收益

状态转移方程:dp[k][i][j] = 第k秒(i,j)出现鼹鼠?1:0 + max(dp[k-1][i][j],dp[k-1][i-1][j],dp[k-1][i][j-1],dp[k-1][i+1][j],dp[k-1][i][j+1])

但,即使用滚动数组,空间的问题解决了,但是还是会超时

想法二:

dp[i] 表示截止到第i个鼹鼠出现的时刻,且打死第i个鼹鼠,最多收益

状态转移方程:dp[i] = 1+max(distance(i,k)<=time[i]-time[k]?dp[k]:0)

复杂度O(m*m) ac

注意:法二种n没有用,不要把m写成n

优化:

记录打到第i只为止答案最大值,可以剪枝


# include <cstdio>
# include <algorithm>
# include <iostream>
using namespace std;
const int M = 10010;
int dp[M], x[M], y[M], tim[M];
int mx[M] = {0};
int main(void)
{
    int n, m; scanf("%d%d", &n, &m);
    for (int i=1; i<=m; i++) scanf("%d%d%d", &tim[i], &x[i], &y[i]);
     
    for (int i=1; i<=m; i++)
    {
        dp[i] = 1; //打死自己这儿的一只
        for (int k=i-1; k>=1; k--)
        {
            if (mx[k] + 1 <= dp[i]) break; // 剪枝
            if (abs(x[i]-x[k]) + abs(y[i]-y[k]) <= tim[i]-tim[k]) dp[i] = max(dp[i], dp[k]+1);
        }
        mx[i] = max(mx[i-1], dp[i]);
    }
    cout << mx[m];
    return 0;
}

NC20271-[SCOI2009]游戏

题意是把n分成几个数的和,问这几个数的lcm一共多少种

lcm来源于n分出来的质因数


# include <cstdio>
# include <algorithm>
# include <iostream>
# include <cstring>
using namespace std;
typedef long long i64;
i64 dp[1010][1010] = {0}; //考虑前i个质数,凑的和为j,几种lcm
bool vis[1010];
int p[1010], cnt;
void getprime(int n) //线性筛质数,筛到n,个数是cnt
{
    memset(vis, false, sizeof(vis));
    cnt = 0;
    for (int i=2; i<=n; i++)
    {
        if (!vis[i]) p[++cnt] = i;
        for (int j=1; j<=cnt && i*p[j]<=n; j++)
        {
            vis[i*p[j]] = true;
            if (i % p[j] == 0) break;
        }
    }
}
int main(void)
{
    int n; cin >> n;
    getprime(n);
    dp[0][0] = 1;
     
    for (int i=1; i<=cnt; i++) //<=n的质数一共cnt
    {
        for (int j=0; j<=n; j++)
        {
            dp[i][j] += dp[i-1][j]; //不选
            for (int cost = p[i]; j+cost<=n; cost *= p[i]) //第i个质数选k个
            {
                dp[i][j+cost] += dp[i-1][j];
            }
        }
    }
     
    long long ans;
    for (int i=0; i<=n; i++) ans +=dp[cnt][i];
    cout << ans;
     
    return 0;
}

★★★★☆

NC20090-[HNOI2011]数学作业

喜欢这道题,题面好理解

调试好久,数据范围很大,modp和i64要时刻注意


// 递推可以和矩阵快速幂结合
# include <cstdio>
# include <algorithm>
# include <iostream>
# include <cstring>
using namespace std;
typedef long long i64;
i64 p; //mod p

struct Matrix {
    int n, m; //n行m列
    i64 M[4][4];
};

void printmm(Matrix C)
{
    for (int i = 1; i <= C.n; i++)
    {
        for (int j = 1; j <= C.m; j++)
            printf("%lld\t", C.M[i][j]);
        printf("\n");
    }
    printf("\n");
}

Matrix mpl(Matrix A, Matrix B)
{
    //printf("Matrix A\n"); printmm(A);
    //printf("Matrix B\n"); printmm(B);
    Matrix C;
    C.n = A.n;
    C.m = B.m;
    memset(C.M, 0, sizeof(C.M));
    for (int i = 1; i <= C.n; i++)
        for (int j = 1; j <= C.m; j++)
            for (int k = 1; k <= A.m; k++)
                C.M[i][j] = (C.M[i][j] + A.M[i][k] * B.M[k][j]%p) % p;
    //printf("Matrix C\n"); printmm(C);
    return C;
}
Matrix pow(Matrix A, long long n) //矩阵快速幂 循环实现
{
    //printf("矩阵的%lld次方\n", n);
    //printf("Matrix\n"); printmm(A);
    Matrix ans;
    ans.n = ans.m = A.n; //方阵
    memset(ans.M, 0, sizeof(ans.M));
    for (int i = 1; i <= ans.n; i++) ans.M[i][i] = 1; //单位阵
    //printf("Matrix ans\n"); printmm(ans);
    while (n > 0)
    {
        if (n % 2) ans = mpl(ans, A);
        A = mpl(A, A);
        n /= 2;
    }
    //printf("Matrix ans\n"); printmm(ans);
    return ans;
}
int main(void)
{
    i64 pow1[20], pow9[20]; // 预处理,i位数的范围是pow1[i]~pow9[i]
    pow1[1] = 1; pow9[1] = 9;
    for (int i = 2; i <= 19; i++)
    {
        pow1[i] = pow1[i - 1] * 10;
        pow9[i] = pow1[i] * 10 - 1;
    }
    pow1[1] = 0;
    
    i64 n;
    cin >> n >> p;
    Matrix D; // D(1)
    D.n = 3; D.m = 1;
    D.M[1][1] = 1; D.M[2][1] = 1; D.M[3][1] = 1;

    // 要乘n次的矩阵
    // f(i) = f(i-1) * 10^x + i (x是i的位数)
    // 所以分段,i是1位的——1~9,2位的——10~99,3位的——100~999,... 一直到19位——1e18
    Matrix E;
    E.n = E.m = 3;
    E.M[1][1] = 1;    E.M[1][2] = 1;    E.M[1][3] = 1;  //E.M[1][1]每次都乘10
    E.M[2][1] = 0;    E.M[2][2] = 1;    E.M[2][3] = 1;
    E.M[3][1] = 0;    E.M[3][2] = 0;    E.M[3][3] = 1;

    Matrix F;
    F.n = F.m = 3;
    memset(F.M, 0, sizeof(F.M));
    for (int i = 1; i <= 3; i++) F.M[i][i] = 1; //单位阵
    //printf("Matrix F\n"); printmm(F);

    int bit = 1;
    i64 lastcnt = 1;
    while (n >= pow1[bit])
    {
        //printf("bit=%d\n", bit);
        E.M[1][1] *= 10;
        E.M[1][1] %= p;
        //printf("Matrix E\n"); printmm(E);
        if (n >= pow9[bit]) // 如果可以到999...99
        {
            F = mpl(pow(E, pow9[bit] - lastcnt), F); //注意不能颠倒,因为矩阵乘法有顺序
            //printf("Matrix F\n"); printmm(F);
        }
        else
        {
            F = mpl(pow(E, n - lastcnt), F);
            //printf("Matrix F\n"); printmm(F);
            break;
        }
        lastcnt = pow9[bit];
        bit++;
    }
    Matrix ans = mpl(F, D);
    cout << (ans.M[1][1]+p)%p;
    return 0;
}

NC20447-[TJOI2013]最长上升子序列

stl的vector+线段树(单点修改,都不用懒惰标记,代码很短)

先根据插入序列弄出最后的排列,按顺序填数,填数规则是统计在这个数之前出现的全部数的最大值+1

统计N次最大值,用线段树优化


# include <iostream>
# include <vector>
# include <algorithm>
# include <cstring>
using namespace std;
const int N = 100010;
int pos[N];// 数字i最后在哪
int whoin[N]; //谁在第i个
vector<int> v;
int sgt[N<<2]; // 只有一个关键词 max
void write(int pos, int num, int rt, int le, int ri)
{
    if (le == ri) //到了pos
    {
        sgt[rt] = num;
        return;
    }
     
    if (pos <=(le+ri)/2) write(pos, num, rt*2, le, (le+ri)/2);
    else write(pos, num, rt*2+1, (le+ri)/2+1, ri);
     
    sgt[rt] = max(sgt[rt*2], sgt[rt*2+1]);
}
int findmax(int L, int R, int rt, int le, int ri) //要查找区间是L~R
{
    if (L > ri || R < le) return 0;
    else if (L <= le && R >= ri) return sgt[rt];
     
    return max(findmax(L, R, rt*2, le, (le+ri)/2), findmax(L, R, rt*2+1, (le+ri)/2+1, ri));
}
int main(void)
{
    int n; cin >> n;
    for(int i = 1; i <= n; i++) //用vector比自己写链表要快
    {
        int pos; cin >> pos;
        v.insert(v.begin() + pos, i);
    }
    for (int i=0; i<n; i++)
    {
        whoin[i+1] = v[i];
        pos[v[i]] = i+1;
    }
    // 建线段树,全为零
    memset(sgt, 0, sizeof(sgt));
    int ans = 0;
    for (int i=1; i<=n; i++) //从1开始依次填
    {
        int m = findmax(1, pos[i]-1, 1, 1, n); //1~pos[i]-1
        write(pos[i], m+1, 1, 1, n); //pos[i] 改为 m+1
        ans = max(ans, m+1);
        cout << ans << endl;
    }
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值