算法E4补题

3 篇文章 0 订阅

主要内容 贪心 DP

A DNA螺旋

现给出两个表示DNA螺旋的串,请你求出它们的最长公共子序列。
输入共三行。
第一行包含一个整数 k(k=0或k=1) 。
接下来两行每行包含一个长度不超过 1000 的串,表示DNA螺旋,含义如上。注意,串长不一定相同。
当 k=0 时,输出一行一个整数,表示最长公共子序列的长度。
当 k=1 时,输出一行一个串,表示具体的最长公共子序列(若不唯一,输出任意一种即可)。

最长公共子序列

#include <stdio.h>
#include <string.h>
#include <ctype.h>
#define max(x, y) (x) > (y) ? (x) : (y)
int k, i, j, lena, lenb, now;
char A[1005], B[1005], ans[1005];
int C[1005][1005];
int main()
{
    scanf("%d ", &k);
    gets(A);
    gets(B);
    lena = strlen(A);
    lenb = strlen(B);
    while (isspace(A[lena - 1]))
    {
        lena--;
    }
    while (isspace(B[lenb - 1]))
    {
        lenb--;
    }
    for (i = 0; i <= lena; i++)
    {
        for (j = 0; j <= lenb; j++)
        {
            if (i == 0 || j == 0)
            {
                C[i][j] = 0;
            }
            else if (A[i - 1] == B[j - 1])
            {
                C[i][j] = C[i - 1][j - 1] + 1;
            }
            else
            {
                C[i][j] = max(C[i][j - 1], C[i - 1][j]);
            }
        }
    }
    if (k == 0)
    {
        printf("%d\n", C[lena][lenb]);
    }
    else
    {
        i = lena;
        j = lenb;
        int nexti, nextj;
        while (C[i][j] != 0)
        {
            if (A[i - 1] == B[j - 1])
            {
                nexti = i - 1;
                nextj = j - 1;
            }
            else
            {
                if (C[i - 1][j] > C[i][j - 1])
                {
                    nexti = i - 1, nextj = j;
                }
                else
                {
                    nexti = i, nextj = j - 1;
                }
            }
            if (C[i][j] != C[nexti][nextj])
            {
                ans[now++] = A[i - 1];
            }
            i = nexti, j = nextj;
        }
        for (i = now - 1; i >= 0; i--)
        {
            printf("%c", ans[i]);
        }
    }
}

在这里插入图片描述

B 深度学习

第一行,一个正整数n,表示矩阵数量。

接下来一行, 有n+1个正整数。第一个数是第一个矩阵的行数,第二个数是第一个矩阵的列数,也是第二个矩阵的行数。依次类推,第n+1个数是第n个矩阵的列数。

输出共两行,第一行表示最少可以进行多少次乘法运算。第二行表示最多进行几次。
矩阵链乘法
在这里插入图片描述

#include <stdio.h>
long long n, i, len, j, k, min, now, NOW, max;
long long q[305], m[305][305], M[305][305];
int main()
{
    scanf("%lld", &n);
    for (i = 0; i <= n; i++)
    {
        scanf("%lld", &q[i]);
    }
    for (i = 1; i <= n; i++)
    {
        m[i][i] = 0;
        M[i][i] = 0;
    }
    for (len = 2; len <= n; len++)
    {
        for (i = 1; i <= n - len + 1; i++)
        {
            j = len + i - 1;
            min = 100000000000000;
            max = -1;
            for (k = i; k < j; k++)
            {
                now = m[i][k] + m[k + 1][j] + q[i - 1] * q[k] * q[j];
                NOW = M[i][k] + M[k + 1][j] + q[i - 1] * q[k] * q[j];
                if (now < min)
                {
                    min = now;
                }
                if (NOW > max)
                {
                    max = NOW;
                }
                m[i][j] = min;
                M[i][j] = max;
            }
        }
    }
    printf("%lld\n%lld", m[1][n], M[1][n]);
}

矩阵链乘法和最优二叉搜索树的状态转移方程相似,循环求结果的过程也比较类似,首先按 l e n len len的长度从1~n做外层循环,再令 i i i 1 1 1遍历到 n − l e n + 1 n - len + 1 nlen+1 j = l e n + i − 1 j = len + i - 1 j=len+i1

因为子问题是 d p [ i ] [ k ] dp[i][k] dp[i][k] d p [ k + 1 ] [ j ] dp[k+1][j] dp[k+1][j],是两个更短的区间,所以时刻需要保证比 [ i , j ] [i,j] [i,j]更短的子问题已经被解决。

C Matrix53只会发表情包

利用最优二叉搜索树,求最小的期望代价。

其中,搜索的深度定义为:
若要搜索的标号是树根结点的标号,则搜索深度为 1,即树根结点的搜索深度为 1
若要搜索的标号为结点 x 的某个子结点的标号,则搜索深度为结点 x 的搜索深度加 1
若搜索到了结点 x 处,但是想要搜索的结点编号比x大(小),且x没有右(左)子树,则判定找不到表情,搜索深度为结点 x 的搜索深度。
在这里插入图片描述

#include <stdio.h>
long long n, i, j, k, root[505][505], len;
long long p[505], q[505], e[505][505], w[505][505], now;
int main()
{
    scanf("%lld", &n);
    for (i = 1; i <= n; i++)
    {
        scanf("%lld", &p[i]);
    }
    for (i = 0; i <= n; i++)
    {
        scanf("%lld", &q[i]);
    }
    for (i = 1; i <= n + 1; i++)
    {
        e[i][i - 1] = 0;//如果搜索不到的深度不是X而是X+1,就是q[i - 1]
        w[i][i - 1] = q[i - 1];
    }
    for (len = 1; len <= n; len++)
    {
        for (i = 1; i <= n - len + 1; i++)
        {
            j = i + len - 1;
            long long min = 1000000000000000000;
            w[i][j] = w[i][j - 1] + p[j] + q[j];
            for (k = i; k <= j; k++)
            {
                now = e[i][k - 1] + e[k + 1][j] + w[i][j];
                if (now < min)
                {
                    min = now;
                    root[i][j] = k;
                }
            }
            e[i][j] = min;
        }
    }
    printf("%lld", e[1][n]);
}

D 道歉的35xirtaM

普通的完全背包。

#include <stdio.h>
long long dp[100005], n, i, j, k;
long long v[1005], w[1005];
long long max(long long a, long long b)
{
    return a > b ? a : b;
}
int main()
{
    scanf("%lld%lld", &n, &k);
    for (i = 1; i <= n; i++)
    {
        scanf("%lld%lld", &v[i], &w[i]);
    }
    for (int i = 1; i <= n; i++)
        for (int j = w[i]; j <= k; j++)
            dp[j] = max(dp[j], dp[j - w[i]] + v[i]);
    printf("%lld\n", dp[k]);
    return 0;
}

0-1背包和完全背包的不同:

从二维数组上区别0-1背包和完全背包也就是状态转移方程就差别在放第i中物品时,完全背包在选择放这个物品时,最优解是 F [ i ] [ j − c [ i ] ] + w [ i ] F[i][j-c[i]]+w[i] F[i][jc[i]]+w[i]即画表格中同行的那一个,而0-1背包比较的是 F [ i − 1 ] [ j − c [ i ] ] + w [ i ] F[i-1][j-c[i]]+w[i] F[i1][jc[i]]+w[i],上一行的那一个。

从一维数组上区别0-1背包和完全背包差别就在循环顺序上,0-1背包必须逆序,因为这样保证了不会重复选择已经选择的物品,而完全背包是顺序,顺序会覆盖以前的状态,所以存在选择多次的情况,也符合完全背包的题意。状态转移方程都为 F [ i ] = m a x ( F [ i ] , d p [ F − c [ i ] ] + v [ i ] ) F[i] = max(F[i],dp[F-c[i]]+v[i]) F[i]=max(F[i],dp[Fc[i]]+v[i])

E 最长波动子数组

#include <stdio.h>
#define MAX(x, y) (x) > (y) ? (x) : (y)
int n, i;
int a[1005];
int dp[1005][2]; //[0]是到i为止最长的上升结尾 [1]是下降结尾
int main()
{
    scanf("%d", &n);
    dp[0][0] = 1, dp[0][1] = 1;
    scanf("%d", &a[0]);
    for (i = 1; i < n; i++)
    {
        scanf("%d", &a[i]);
        if (a[i] > a[i - 1])
        {
            dp[i][0] = dp[i - 1][1] + 1;
            dp[i][1] = dp[i - 1][1];
        }
        else if (a[i] < a[i - 1])
        {
            dp[i][1] = dp[i - 1][0] + 1;
            dp[i][0] = dp[i - 1][0];
        }
        else
        {
            dp[i][0] = dp[i - 1][0];
            dp[i][1] = dp[i - 1][1];
        }
    }
    printf("%d", MAX(dp[n - 1][0], dp[n - 1][1]));
}

F 更改字符串

不能替换的最短编辑距离

#include <stdio.h>
#include <string.h>
#define MIN(x, y) (x) < (y) ? (x) : (y)
char a[2005], b[2005];
int dp[2005][2005];
int i, j, lena, lenb;
int main()
{
    scanf("%s%s", a, b);
    lena = strlen(a);
    lenb = strlen(b);
    for (i = 0; i <= lena; i++)
    {
        dp[i][0] = i;
    }
    for (i = 0; i <= lenb; i++)
    {
        dp[0][i] = i;
    }
    for (i = 1; i <= lena; i++)
    {
        for (j = 1; j <= lenb; j++)
        {
            if (a[i - 1] == b[j - 1])
            {
                dp[i][j] = dp[i - 1][j - 1];
            }
            else
            {
                dp[i][j] = MIN(dp[i - 1][j] + 1, dp[i][j - 1] + 1);
            }
        }
    }
    printf("%d", dp[lena][lenb]);
}

在这里插入图片描述
在这里插入图片描述
用C(i,j)表示序列A[0]…A[i]和序列B[0]…B[j]的最少编辑操作次数

G 贪心只能过样例

在这里插入图片描述
经过观察, n / 2 , n / 4 , n / 4 n/2,n/4,n/4 n/2n/4n/4三个数或者 n / 2 , n / 2 n/2,n/2 n/2n/2两个数恰好满足两条件,
同时为了减小对 L C M LCM LCM的影响,尽量增加1的数量,且除去所有1,剩下3个数时,可以通过讨论对4取模的结果,尽量构造上面两种情况。

#include <stdio.h>
int T, n, k, i, j;
int main()
{
    scanf("%d", &T);
    while (T--)
    {
        scanf("%d%d", &n, &k);
        for (i = 1; i <= k - 3; i++)
        {
            printf("1 ");
        }
        n -= k - 3;
        if (n % 4 == 0)
        {
            printf("%d %d %d\n", n / 2, n / 4, n / 4);
        }
        else if (n % 2 == 0)
        {
            printf("2 %d %d\n", (n - 2) / 2, (n - 2) / 2);
        }
        else
        {
            printf("1 %d %d\n", (n - 1) / 2, (n - 1) / 2);
        }
    }
}

H 双人成行

有一款名为双人成行的双人游戏,两个玩家分别控制游戏中的两个人物。每个人物有一个能力,可以用a1,a2表示。初始时,两人的能力都为0。

游戏是以天为单位进行的,每一天这两个人的能力都会分别提升b1,b2;此外,每天可以获得一个天赋点,让b1或者b2的值增加1。b1,b2的初始值一开始也为0。

等两人的能力提升到一定的程度后,就可以打败 Boss 通关。通过查询攻略可知,只要满足a1≥l1,a2≥l2,就能通关。

现在请你计算出,最快几天才可以通关。注意,每一天都是先增加天赋点,再提升的能力。

#include <stdio.h>
#include <string.h>
#define MAX(x, y) (x) > (y) ? (x) : (y)
int T, i;
int l1, l2;
int main()
{
    scanf("%d", &T);
    while (T--)
    {
        scanf("%d%d", &l1, &l2);
        int sum = l1 + l2, now = 0;
        for (i = 1;; i++)
        {
            now += i;
            if (now >= sum)
            {
                break;
            }
        }
        printf("%d\n", i);
    }
}

一共有n天,每天都可以选择给b1或b2加点,不论为谁加点,第1天的加点为最后总点数的贡献是n,第2天的加点为总点数的贡献是n-1…
所以最后的总点数一定是 ( 1 + n ) n / 2 (1+n)n/2 (1+n)n/2
可以证明如果 1 + 2 + 3 + . . . + n > = l 1 + l 2 1+2+3+...+n>=l_1+l_2 1+2+3+...+n>=l1+l2,一定有一种分配方式,使一部分>=l1,另一部分>=l2。
所以只要求第一个使总点数达标的n就可以。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值