3169 字
16 分钟
经验-2025年FDU CS夏令营
⚠️ 注:本文仅为作者个人日记,不构成任何形式的指南或保证。本文信息可能过时或不适用于你的具体情况。因参考本文产生的任何后果,作者概不负责。

时间:2025年8月25日-8月28日

基本信息#

Day 1: 报到 / 志愿填报#

TODO

Day 2: 机试#

电脑上有 VS、VSCode(提供 CodeRunner 插件及使用方法)、Dev-cpp 等IDE,类似 OI 赛制,无法得知评测实际得分,支持 C++17

Q1 考试分数#

搞崩我机试的罪魁祸首。。。考场上的我的罪过不是做不出来这题,而是没判断出来这题我的思路不可能做的出来。。。

考点:状态压缩DP

题面#

小明参加了多门科目的考试,他只记得各科得分拼接成的字符串 s,但忘记了具体的分割方式。

已知:

  • m 门科目的满分分别是 r[1], r[2], ..., r[m]
  • 其余科目的满分均为 t

请你帮助小明解析字符串 s,将其分割为若干非负整数(表示每科得分),使得:

  1. 每个得分不超过其对应科目的满分;
  2. 恰好使用 r 中的每个满分值一次(即有 m 个科目使用 r[i] 作为满分);
  3. 其余科目使用满分 t
  4. 所有得分拼接后等于 s,且每个数无前导零(除非是单独的 “0”)。

在所有合法方案中,求最大可能的总分。数据保证存在合法方案。

输入格式:

第一行三个整数:n(字符串长度),t(通用满分),m(特殊满分科目数)

第二行:一个长度为 n 的字符串 s

第三行:m 个整数,表示数组 r

输出格式:

一个整数,表示最大可能总分

样例:

输入

6 100 2
859271
90 80

输出

248

参考解答#

// 答案借助AI生成,仅供参考
// 时间复杂度 O(n² × 2^m × m)
// 空间复杂度 O(n × 2^m)
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, t, m;
cin >> n >> t >> m;
string s;
cin >> s;
vector<int> r(m);
for (int i = 0; i < m; ++i) {
cin >> r[i];
}
// total_mask: 所有特殊满分科目的状态总数
int total_mask = 1 << m;
// dp[i][mask]: 前 i 个字符,使用了 r 的 mask 集合时的最大总分
vector<vector<long long>> dp(n + 1, vector<long long>(total_mask, LLONG_MIN));
dp[0][0] = 0;
// 预处理:检查从 start 开始长度为 len 的子串是否合法,并获取数值
auto parseNum = [&](int start, int len) -> long long {
if (len == 0) return -1;
if (len > 1 && s[start] == '0') return -1; // 前导零非法
long long num = 0;
for (int i = start; i < start + len; ++i) {
if (!isdigit(s[i])) return -1;
num = num * 10 + (s[i] - '0');
}
return num;
};
// 枚举结束位置 i
for (int i = 1; i <= n; ++i) {
// 枚举起始位置 j
for (int j = 0; j < i; ++j) {
int len = i - j;
long long num = parseNum(j, len);
if (num == -1) continue; // 非法数字
// 枚举之前的状态:dp[j][mask]
for (int mask = 0; mask < total_mask; ++mask) {
if (dp[j][mask] == LLONG_MIN) continue;
// 选项1:使用 t 作为通用满分(剩余科目)
if (num <= t) {
if (dp[i][mask] < dp[j][mask] + num) {
dp[i][mask] = dp[j][mask] + num;
}
}
// 选项2:使用 r 中的某个未使用的特殊满分
for (int k = 0; k < m; ++k) {
if ((mask & (1 << k)) == 0) { // r[k] 未使用
if (num <= r[k]) {
int new_mask = mask | (1 << k);
if (dp[i][new_mask] < dp[j][mask] + num) {
dp[i][new_mask] = dp[j][mask] + num;
}
}
}
}
}
}
}
// 输出结果:处理完所有字符,且所有特殊满分科目都被使用
cout << dp[n][total_mask - 1] << endl;
return 0;
}

注意:面试时问我如何进一步优化空间。之前有人说滚动数组,但我目前没想明白在这个题上是怎么弄。

Q2 中位数#

这个还真不难。考场上我基本已经完成了大半…考场上的我怎么会觉得使用set不行呢???明明知道set是有序的。。。

我当时想到流式数据的中位数可以用对顶堆维护,是在力扣hot100学到的。不过删除元素又该怎么办呢?于是考场上的我陷入了深深的迷茫。。。。。。

题面#

题目描述:

给定一个长度为 nn 的整数数组 aa,以及一个滑动窗口大小 mm。窗口从数组最左端开始,每次向右滑动一个位置,直到覆盖最右端,共产生 nm+1n - m + 1 个窗口。

对于每个窗口,定义“独立数”为在该窗口中恰好出现一次的数字。

将每个窗口的所有独立数按升序排序,取排序后序列的中位数:我们这里取下标为 k12\left\lfloor \frac{k-1}{2} \right\rfloor 的元素(kk 为独立数个数)。若 k=0k = 0,请输出 “No”

请输出每个窗口的中位数。

输入格式:

第一行两个整数:nn(数组长度),mm(窗口大小)

第二行 nn 个整数:表示数组 aa

输出格式:

一行 nm+1n - m + 1 个整数,表示每个窗口的独立数中位数,用空格隔开。

数据范围:

  • 1mn1051 \leq m \leq n \leq 10^5
  • 1a[i]1091 \leq a[i] \leq 10^9

样例输入:

7 3
1 2 2 3 1 4 5

样例输出:

1 3 2 3 4

参考解答#

// 复杂度分析见结尾
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int main() {
int n, m;
cin >> n >> m;
vector<int> a(n);
for (int i = 0; i < n; i++) {
cin >> a[i];
}
unordered_map<int, int> freq;
set<int> independent;
// 初始化第一个窗口
for (int i = 0; i < m; i++) {
freq[a[i]]++;
}
for (auto& [num, cnt] : freq) {
if (cnt == 1) {
independent.insert(num);
}
}
vector<int> ans;
// 处理每个窗口
for (int i = 0; i <= n - m; i++) {
// 计算当前窗口中位数
if (independent.empty()) {
ans.push_back(-1);
} else {
auto it = independent.begin();
int k = independent.size();
int idx = (k - 1) / 2;
advance(it, idx);
ans.push_back(*it);
}
// 滑动窗口
if (i == n - m) break;
int left = a[i];
int right = a[i + m];
int old_freq_left = freq[left];
freq[left]--;
int new_freq_left = freq[left];
if (old_freq_left == 1) {
independent.erase(left);
} else if (old_freq_left == 2) {
independent.insert(left);
}
if (new_freq_left == 0) {
freq.erase(left);
}
int old_freq_right = freq.count(right) ? freq[right] : 0;
freq[right]++;
int new_freq_right = freq[right];
if (old_freq_right == 0) {
independent.insert(right);
} else if (old_freq_right == 1) {
independent.erase(right);
}
}
// 输出结果:-1 转换为 "No"
for (int i = 0; i < ans.size(); i++) {
if (i > 0) cout << " ";
if (ans[i] == -1) {
cout << "No";
} else {
cout << ans[i];
}
}
cout << endl;
return 0;
}

最坏 O(nm)

实际上集合维护是 O(nlogm) 的,但每次找中位数都要 O(k) (k:窗口内独立数个数),最坏可达 O(m)

对顶堆不能删除元素,所以就不能用了么?并非如此,可以用 multiset 模拟堆。还是对顶堆的思想,维护两个 multiset 即可。这样每次插入/删除/查中位数都是 O(log m),这样总的时间复杂度也可以保证 O(nlogm)

Q3 树的重心#

OI-wiki 上看到过。没认真看。这下服气了。

题面#

题目描述

给定一棵由 nn 个节点组成的无根树,我们定义一个节点的平衡值为:删除该节点后,所形成的若干连通块(子树)中,节点数的最大值。

一棵树的重心是指使该平衡值最小的节点。可以证明,一棵树最多有两个重心,且若有两个重心,则它们相邻。

现在,请你找出这棵树的所有重心。

输入格式

第一行一个正整数 nn,表示树的节点数。

接下来 n1n - 1 行,每行两个正整数 uuvv,表示树上存在一条连接节点 uuvv 的边。

输出格式

输出共一行,按升序输出所有重心的编号,用空格隔开。

样例输入

5
1 2
2 3
3 4
4 5

样例输出

3

参考解答#

详见 OI-wiki

// 时间复杂度: O(n)
// 空间复杂度: O(n)
const int MAXN = 50005;
int n;
// 这份代码默认节点编号从 1 开始,即 i ∈ [1,n]
int siz[MAXN], // 这个节点的「大小」(所有子树上节点数 + 该节点)
weight[MAXN]; // 这个节点的「重量」,即所有子树「大小」的最大值
vector<int> centroids; // 用于记录树的重心(存的是节点编号)
vector<int> g[MAXN];
void dfs(int cur, int fa) { // cur 表示当前节点 (current)
siz[cur] = 1;
weight[cur] = 0;
for (int v : g[cur]) {
if (v != fa) { // v 表示这条有向边所通向的节点
dfs(v, cur);
siz[cur] += siz[v];
weight[cur] = max(weight[cur], siz[v]);
}
}
weight[cur] = max(weight[cur], n - siz[cur]);
if (weight[cur] <= n / 2) { // 依照树的重心的定义统计
centroids.push_back(cur);
}
}
void get_centroids() { dfs(1, 0); }

Q4 万年历#

Q5 Z-order 曲线 / Morton 序 坐标映射#

考试的时候看都没看。没想到并不困难。

题面#

题目描述

给定一个边长为 2n2^n 的正方形方阵(行和列均从 0 开始编号)。该方阵的构造规则如下:

  • 将整个 2n×2n2^n \times 2^n 的方阵划分为四个 2n1×2n12^{n-1} \times 2^{n-1} 的子方阵,分别称为左上、右上、左下、右下子方阵。
  • 这四个子方阵按照 左上 → 右上 → 左下 → 右下 的顺序,依次填入连续的整数,从 0 开始。
  • 每个子方阵内部的填充方式递归地遵循相同的规则,直到子方阵的边长为 20=12^0 = 1(即单个格子)为止。

例如,当 n=2n = 2 时,方阵如下所示:

0 1 4 5
2 3 6 7
8 9 12 13
10 11 14 15

现在,给定参数 nn 以及坐标 (x,y)(x, y),请求出该坐标处方阵中的数值。

输入格式

输入共两行。

  • 第一行包含一个整数 nn
  • 第二行包含两个整数 xxyy,表示查询的坐标。

输出格式

输出一个整数,表示坐标 (x,y)(x, y) 处的数值。

样例输入

2
3 1

样例输出

11

参考解答#

直接构造整个方阵在 nn 较大时当然会超出时间和空间限制。递归即可解决。

// 时间 O(n)
// 空间 O(n)
#include <iostream>
using namespace std;
long long solve(int n, long long x, long long y) {
if (n == 0) {
return 0;
}
long long mid = 1LL << (n - 1); // 子方阵边长:2^(n-1)
long long area_size = mid * mid; // 每个子方阵包含的格子数
bool in_top = (x < mid);
bool in_left = (y < mid);
if (in_top && in_left) {
// 左上
return solve(n - 1, x, y);
} else if (in_top && !in_left) {
// 右上
return area_size + solve(n - 1, x, y - mid);
} else if (!in_top && in_left) {
// 左下
return 2 * area_size + solve(n - 1, x - mid, y);
} else {
// 右下
return 3 * area_size + solve(n - 1, x - mid, y - mid);
}
}
int main() {
int n;
long long x, y;
cin >> n >> x >> y;
cout << solve(n, x, y) << endl;
return 0;
}

实际上,Z-order 曲线 / Morton 码可以通过 位交织(bit interleaving) 直接计算:

long long z_order(int n, long long x, long long y) {
long long result = 0;
for (int i = 0; i < n; i++) {
long long bit_x = (x >> i) & 1;
long long bit_y = (y >> i) & 1;
result |= (bit_y << (2 * i)) | (bit_x << (2 * i + 1));
}
return result;
}

Day 2: 英语口试#

共5分钟,简短自我介绍,然后是简单提问,介绍项目等。

Day 4: 面试#

允许带简历。也可以不带。15分钟。

7个面试官,但只有少数老师在提问。个人感觉负责提问的老师对我非常友善,但我自己非常不争气。。。

  • 机试 问机试成绩为什么这么差。(我开始解释怎么从第一题在上浪费了大量时间。)于是紧接着问现在有思路了嘛,讲讲第一题思路,如何进一步优化等。

  • 数学 一直掷硬币直到连续两次正面,问次数期望。

  • 项目 问了实现细节、模型效果。

  • AI (看我学了深度学习)卷积神经网络的效果为什么好?它的核心特点是什么? (我的某个项目用到了随机森林)介绍下随机森林算法?知不知道我们学校谁随机森林做的比较好?(看我是南大的想让我回答周老师但我没敢答。。。)

  • 其他聊天 问如何看待本科生科研和精力分配(看我没什么科研经历以及简历上写了很多乱七八糟的东西hhh) 问为什么报我们复旦?(我提到本校还没有offer)如果本校候补到了,和复旦你选哪个? 问对AIGC了解多少(感觉是老师觉得实在没什么可以问的了,问我啥我啥不会>_<)

结果#

TODO,铁定寄了

经验-2025年FDU CS夏令营
https://stivine.github.io/posts/experience-fdu-cs-summer-camp-2025/
作者
藤君
发布于
2025-08-28
许可协议
CC BY-NC-SA 4.0