1. 加分二叉树
来源:NOIP2003提高组 https://ac.nowcoder.com/acm/contest/251/C
算法知识点: 区间DP,二叉树的遍历
复杂度: &preview=true)
解题思路:
状态表示:f[i][j] 表示中序遍历是 w[i ~ j] 的所有二叉树的得分的最大值。
状态计算:f[i][j] = max(f[i][k - 1] * f[k + 1][j] + w[k]),即将f[i][j]表示的二叉树集合按根节点分类,则根节点在 k 时的最大得分即为 f[i][k - 1] * f[k + 1][j] + w[k],则f[i][j]即为遍历 k 所取到的最大值。
在计算每个状态的过程中,记录每个区间的最大值所对应的根节点编号。
那么最后就可以通过DFS求出最大加分二叉树的前序遍历了。
C++ 代码:
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
typedef pair <int, int> PII; const int N = 50;
int n;
int w[N];
unsigned f[N][N];
int root[N][N];
void dfs(int l, int r)
{
if (l > r) return;
int k = root[l][r];
printf("%d ", k, f[l][r]);
dfs(l, k - 1);
dfs(k + 1, r);
}
int main()
{
scanf("%d", &n);
for (int i = 1; i <= n; i++) scanf("%d", &w[i]);
for (int len = 1; len <= n; len++)
for (int l = 1; l + len - 1 <= n; l++)
{
int r = l + len - 1;
for (int k = l; k <= r; k++)
{
int left = k == l ? 1 : f[l][k - 1];
int right = k == r ? 1 : f[k + 1][r];
int score = left *right + w[k];
if (l == r) score = w[k];
if (f[l][r] < score)
{
f[l][r] = score;
root[l][r] = k;
}
}
}
printf("%d\n", f[1][n]);
dfs(1, n);
puts("");
return 0;
}
2. 合唱队形
来源:NOIP2004提高组 https://ac.nowcoder.com/acm/contest/252/C
算法知识点: 线性DP,最长上升子序列
复杂度: &preview=true)
解题思路:
假设最优解的中心是第 个人,则
一定是以
结尾的最长上升子序列。
同理, 也一定是以
结尾的最长上升子序列。
因此可以先预处理出:
- 从前往后以每个点结尾的最长上升子序列长度
f[i]; - 从后往前以每个点结尾的最长上升子序列长度
g[i];
那么以 为中心的最大长度就是
f[k] + g[k] - 1,遍历 k = 1, 2, ..., n 取最大值即为答案。
C++ 代码:
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std; const int N = 110;
int n;
int h[N];
int f[N], g[N];
int main()
{
scanf("%d", &n);
for (int i = 1; i <= n; i++) scanf("%d", &h[i]);
for (int i = 1; i <= n; i++)
{
f[i] = 1;
for (int j = 1; j < i; j++)
if (h[j] < h[i])
f[i] = max(f[i], f[j] + 1);
}
for (int i = n; i; i--)
{
g[i] = 1;
for (int j = n; j > i; j--)
if (h[j] < h[i])
g[i] = max(g[i], g[j] + 1);
}
int res = 0;
for (int i = 1; i <= n; i++) res = max(res, f[i] + g[i] - 1);
printf("%d\n", n - res);
return 0;
}
3.过河
来源:NOIP2005提高组 https://ac.nowcoder.com/acm/contest/253/B
算法知识点: 动态规划,数学
复杂度: &preview=true)
解题思路:
如果不考虑 的范围,那么就是一道简单的DP问题:
- 状态表示
f[i]表示走到位置i,踩到的石头个数的最小值; - 状态计算
f[i] = min(f[j]) + w[i], 其中w[i]表示第i个位置是否有石头,j在S到T之间。
那么当 很大时该怎么办呢,我们发现虽然
是
级别,但石头总数很少,最多只有
个,因此两个石头之间可能有很长的“空隙”。
接下来分两种情况讨论:
- 如果
S == T,那么走法唯一,石头位置是S整数倍的一定会被踩,否则一定不会被踩,直接遍历一遍所有石头即可; - 如果
S != T,那么我们考虑不能被S, S+1, S+2, ..., T所表示的数有哪些,如果我们只用两个相邻的数,那么不能被表示的数的最大值是
,因此所有大于等于
的数一定可以被
表示出来,当
时取到最大值,此时
,因此所有大于等于72的数,一定可以被
S, S+1, S+2, ..., T表示出来。当第一次越过第个石头时,青蛙的位置一定在该石头右侧十步以内;当即将跳过第
个石头时,青蛙一定在第
个石头左侧十步以内。那么当中间部分的长度大于100时我们可以将线段的长度缩短为100,得到的结果是等价的。那么此时最多只会用到
个位置,复杂度可以接受了。
C++ 代码:
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std; const int N = 10020;
int n, S, T, m;
int stones[N];
int w[N], f[N];
int main()
{
scanf("%d%d%d%d", &n, &S, &T, &m);
if (S == T)
{
int res = 0;
while (m--)
{
int x;
scanf("%d", &x);
if (x % S == 0) res++;
}
printf("%d\n", res);
}
else
{
for (int i = 0; i < m; i++) scanf("%d", &stones[i]);
sort(stones, stones + m);
int last = 0, k = 0;
for (int i = 0; i < m; i++)
{
for (int j = 0; j < min(stones[i] - last, 100); j++)
w[++k] = 0;
w[k] = 1;
last = stones[i];
}
for (int i = 1; i <= k + 10; i++)
{
f[i] = 1e9;
for (int j = S; j <= T; j++)
if (i - j >= 0)
f[i] = min(f[i], f[i - j] + w[i]);
}
int res = 1e9;
for (int i = k + 1; i <= k + 10; i++) res = min(res, f[i]);
printf("%d\n", res);
}
return 0;
}
4. 金明的预算方案
来源:NOIP2006提高组 https://ac.nowcoder.com/acm/contest/254/B
算法知识点: DP,分组背包问题
复杂度: &preview=true)
解题思路:
可以将每个主件及其附件看作一个物品组,记主件为 ,两个附件为
,则最多一共有4种组合:
这四种组合是互斥的,最多只能从中选一种,因此可以将每种组合看作一个物品,那么问题就变成了分组背包问题。
C++ 代码:
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
typedef pair <int, int> PII;
const int N = 70,
M = 32010;
int m, n;
PII a[N];
vector<PII> b[N];
int f[M];
int main()
{
scanf("%d%d", &m, &n);
for (int i = 1; i <= n; i++)
{
int v, w, p;
scanf("%d%d%d", &v, &w, &p);
w *= v;
if (!p) a[i] = {
v, w
};
else b[p].push_back(
{
v, w
});
}
for (int i = 1; i <= n; i++)
{
if (!a[i].first) continue;
for (int j = m; j >= 0; j--)
for (int k = 0; k < 1 << b[i].size(); k++)
{
int v = a[i].first, w = a[i].second;
for (int u = 0; u < b[i].size(); u++)
if (k >> u &1)
{
v += b[i][u].first;
w += b[i][u].second;
}
if (j >= v) f[j] = max(f[j], f[j - v] + w);
}
}
printf("%d\n", f[m]);
return 0;
}
另外,牛客暑期NOIP真题班限时免费报名
报名链接:https://www.nowcoder.com/courses/cover/live/248
报名优惠券:DCYxdCJ

京公网安备 11010502036488号