度小满2024春招算法方向第1批
祖先
题目描述
你的实验室中诞生了一种新的无性繁殖生物,这种生物每一个都有且仅有一个父亲(注意,其中有且仅有 \(1\) 号生物的父亲无法追踪到了)。为了研究方便,你定义一个生物的 \(1\) 级祖先即为其父亲,而其 \(k\) 级祖先为 \(k-1\) 级祖先的父亲 \((k≥2)\)。例如,\(2\) 级祖先即为父亲的父亲。现在你想知道 一些生物的若干级父亲。
输入描述
第一行两个以空格给开的正整数 \(n\) 和 \(q\),表示生物总数和询问次数
第二行有 \(n-1\) 个由空格隔开的正整数,\(i\) 号生物的父亲的编号为 \(a_i\)。其中 \(1\) 号生物的父亲已经无法追踪到了。
接下来 \(q\) 行,每行两个由空格隔开的正整数 \(x ,k\) ,表示询问第 \(x\) 号生物的 \(k\) 级祖先。
- \(2≤n,q≤50000\)
- $1≤ a_i < i $
- \(1≤x, k≤n\)
输出描述
输出 \(q\) 行,每行依次对应一个询问的答案,即第 \(x\) 号生物的 \(k\) 级祖先。如果无法追踪到该祖先,则输出 \(0\)。
解题思路
递归往上遍历,对已知信息进行缓存。
代码实现
// mp[x][k] = x号生物的k级祖先
unordered_map<int, unordered_map<int, int>> mp;
// 找到x号生物的k级祖先
int dfs(int x, int k) {
int &v = mp[x][k];
// 已被缓存则直接返回
if (v)return v;
// 递归往上遍历
return v = dfs(mp[x][1], k - 1);
}
int main() {
int n, q;
scanf("%d%d", &n, &q);
// 1号生物的父亲标记为-1
mp[1][1] = -1;
for (int i = 2, p; i <= n; i++) {
scanf("%d", &p);
mp[i][1] = p;
}
for (int i = 0, x, k; i < q; i++) {
scanf("%d%d", &x, &k);
printf("%d\n", max(dfs(x, k), 0));
}
return 0;
}
时间复杂度:\(O(qlog(n))\)
空间复杂度:\(O(nlog(n))\)
阶梯
题目描述
你有 \(n\) 个箱子,它们的高度分别为 \(a_i\),你想要用它们做出一个尽可能长的阶梯。但是你对最长的阶梯长度不感兴趣,你只对其方案数感兴趣。 形式化地,给出长度为 \(n\) 的序列 \({ai}\),从中挑选出子序列 \({bi}\),满足对所有合法下标 \(i\),有 \(b_i<b_i+1\) 成立(即单调递增,如果子序列 \({b_i}\) 长度为 \(1\),亦视为满足此条件)。
在这些子序列中,长度为最大长度的子序列有多少个?
子序列:某个序列的子序列是从最初序列通过去除某些元素但不破坏余下元素的相对位置(在前或在后)而形成的新序列。
我们认为两个子序列相同,当且仅当所有数都是从相同位置选出来的。而对于序列 \({1,2,2,6}\),选择第 \(2\) 个和第 \(4\) 个形成子序列 \({2,6}\),选择第 \(3\) 个和第 \(4\) 个形成子序列 \({2,6}\),虽然形式相同但仍视为不同的序列。
输入描述
第一行一个正整数 \(n\) 表示序列长度。
第二行 \(n\) 个由空格隔开的正整数 \(a_i\) ,依次表示 \(a_1\) 到 \(a_n\)。
对于 \(100\%\) 的数据,\(1≤n≤3000\),\(a_i≤10^9\)。
输出描述
一行一个数,表示答案,对 \(10^9+7\) 取模。
解题思路
最长递增子序列问题的变形。
令 \(dp[i]\) = 以 \(a_i\) 结尾的最长递增子序列的长度,则 \(dp[i] = max(dp[1], dp[2], ..., dp[i-1])\)。
令 \(cnt[i]\) = 以 \(a_i\) 结尾的最长递增子序列的数量,则对于 \(j<i\),若 \(dp[i]<dp[j]+1\),则 \(cnt[i]=cnt[j]\),若 \(dp[i]=dp[j]+1\),则 \(cnt[i]+=cnt[j]\)。
代码实现
typedef long long ll;
const int N = 3e3 + 5, MOD = 1e9 + 7;
int dp[N], cnt[N], a[N];
int main() {
int n;
scanf("%d", &n);
for (int i = 1; i <= n; i++)
scanf("%d", &a[i]);
int ml = 0;
for (int i = 1; i <= n; i++) {
// dp和cnt的初始值
dp[i] = cnt[i] = 1;
for (int j = 1; j < i; j++)
if (a[j] < a[i]) {
if (dp[i] < dp[j] + 1)
dp[i] = dp[j] + 1, cnt[i] = cnt[j];
else if (dp[i] == dp[j] + 1)
cnt[i] += cnt[j];
}
// 记录最长递增子序列的长度
ml = max(ml, dp[i]);
}
// 累计所有长度为ml的最长递增子序列的长度
ll res = 0;
for (int i = 1; i <= n; i++)
if (dp[i] == ml)res = (res + cnt[i]) % MOD;
printf("%lld", res);
return 0;
}
时间复杂度:\(O(n^2)\)
空间复杂度:\(O(n)\)
END
文章文档:公众号 字节幺零二四 回复关键字可获取本文文档。
题目来源:度小满2024春招算法方向第1批
文章声明:题目来源 牛客 平台,如有侵权,请联系删除!

度小满2024春招算法方向第1批:祖先、阶梯(最长递增子序列),涉及动态规划、深度优先搜索、记忆化搜索、树等。
浙公网安备 33010602011771号