主要内容 贪心 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 n−len+1, j = l e n + i − 1 j = len + i - 1 j=len+i−1。
因为子问题是 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][j−c[i]]+w[i]即画表格中同行的那一个,而0-1背包比较的是 F [ i − 1 ] [ j − c [ i ] ] + w [ i ] F[i-1][j-c[i]]+w[i] F[i−1][j−c[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[F−c[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/2,n/4,n/4三个数或者
n
/
2
,
n
/
2
n/2,n/2
n/2,n/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就可以。