记录 | 大学里的最后一段时光 #日记楼
Vanitas Vanitatum Et Omnia Vanitas.
虚空的虚空,一切尽为虚空。《般若波罗蜜多心经》亦有言:“观自在菩萨,行深般若波罗蜜多时,照见五蕴皆空,度一切苦厄”。
古今中外,英雄豪杰无不如过江之鲫,于兴盛之时,凭天时地利人和,独领风骚,傲立潮头。殊不知千年之后,虽有“樯橹灰飞烟灭”之气魄,亦不免消失于滚滚历史洪流。
泛泛之辈如我,唯有专注于当下,珍惜自我所有之物,珍惜身边亲近的人,默默地走完本科的最后一段旅程。2025 年 06 月至 2026 年 06 月,我愿在此处不定期地记录下属于自己的一切。
一切皆空,唯有当下愈加贵重。万事喧嚣,愿有一方清静独处之处。愿此处小楼能成为承载我接下来一年的喜怒哀乐,愿自己能够在互联网的一角,留下属于自己的一点点痕迹。
开楼就用我最喜欢的一部动画《可塑性记忆 (プラスチックメモリス)》中的题词送给大家,也送给自己:

提示:可能有时候 lz 会有很负面的情绪,或者不恰当的表述,先向大家表示抱歉,如果楼主说了什么不太好的话或者令大家伤心的话,提示一下 lz 或者屏蔽楼主就好了,先向大家表示歉意,对不起,谢谢大家 🥰
楼里会有什么?
- 可愛いアイラちゃん
- 各种可能的动漫截图
- 每天的生活
- 风景图?周边地图?
- 🐷 😋 🤡
- lz 的焦虑和不安 (很抱歉,有时候控制不了自己 🥲)
- 可能的焦虑传播 (对不起,请一定要提醒 lz 删除(私信发帖都可以))
- 可能的压力和不安
cy ![]()
是新日记楼!
祝 lz 生活顺利🥹
长恨此身非我有 何时忘却营营
开篇先给出自己的假期计划吧!
古人有云:“取法乎上,仅得其中。取法乎中,仅得其下。”所以目标要兼具挑战性和可行性。
暑假的三个月打算:
上午 7:00 左右起床
上午:leetcode,复习数据结构,与其他 408 科目,顺序打算为数据结构 -> 计算机组成 -> 操作系统 -> 计算机网络。这些科目アイラちゃん在期末都是 90 分左右,因此希望会顺利一些 QAQ,为考研/推免都做好两手准备 🫠
中午/下午:做实验室实习的项目/阅读需要阅读的论文,或者在没有活的时候做一做 15445/6.824,学习 C++/Go。毕竟有希望就要去争取!
晚上:继续复习高数/线代。等到线性代数复习结束后再开概率论和数理统计。并且最后复习一些单词(不过英语应该不需要太担心,アイラちゃん很努力地通过了 Toefl, R25L25S25W26!)
希望自己能完成这个暑假的计划,fight on!!!
cy ![]()
アイラちゃん(指插图中的艾拉)今日もちょ可愛いです 🥰

🥰
アイラちゃん 🥰 🥰
艾拉可爱捏
如果能让アイラちゃん做我的彼女我就算住大别野也愿意口牙 ![]()
2025 年 5 月 30 日 星期五 天气:晴/多云
上午刷 leetcode,复习了一下 dijkstra 算法,正好今天的每日一题也是 dijkstra 算法~
C++
class Solution {
int n;
public:
void bfs(const vector<vector<int>> & graph, priority_queue<pair<int,int>, vector<pair<int,int>>, greater<pair<int,int>>> & pq, vector<int> & distance, vector<int> & visited, const int & node) {
visited = vector<int>(n, 0);
pq.emplace(0, node);
visited[node] = 1;
distance[node] = 0;
while(!pq.empty()) {
const auto [dist, cur] = pq.top();
pq.pop();
for(const auto & adj: graph[cur]) {
if(distance[adj] > distance[cur] + 1) {
distance[adj] = distance[cur] + 1;
pq.emplace(distance[adj], adj);
}
}
}
}
int closestMeetingNode(vector<int>& edges, int node1, int node2) {
n = edges.size();
vector<vector<int>> graph(n);
for(int i = 0; i < n; i++) {
if(edges[i] == -1) continue;
graph[i].emplace_back(edges[i]);
}
priority_queue<pair<int,int>, vector<pair<int,int>>, greater<pair<int,int>>> pq;
vector<int> distance1(n, INT_MAX / 2);
vector<int> distance2(n, INT_MAX / 2);
vector<int> visited;
bfs(graph, pq, distance1, visited, node1);
bfs(graph, pq, distance2, visited, node2);
// Get the distance. Then check the answer.
int ans_dist = INT_MAX / 2;
int ans = -1;
for(int i = 0; i < n; i++) {
int tmp_ans = max(distance1[i], distance2[i]);
if(ans_dist > tmp_ans) {
ans_dist = tmp_ans;
ans = i;
}
else if(ans_dist == tmp_ans) {
ans = min(ans, i);
}
}
return ans == INT_MAX / 2 ? -1 : ans;
}
};
C++
class Graph {
vector<vector<pair<int,int>>>graph;
vector<int> visited;
vector<int> distance;
int n;
public:
Graph(int n, vector<vector<int>>& edges) {
graph = vector<vector<pair<int,int>>>(n);
for(const auto & edge: edges) {
int from = edge[0], to = edge[1], edgeCost = edge[2];
graph[from].emplace_back(to, edgeCost);
}
Graph::n = n;
}
void addEdge(vector<int> edge) {
int from = edge[0], to = edge[1], edgeCost = edge[2];
graph[from].emplace_back(to, edgeCost);
}
int shortestPath(int node1, int node2) {
priority_queue<pair<int,int>, vector<pair<int,int>>, greater<pair<int,int>>> pq;
visited = vector<int>(n, 0);
distance = vector<int>(n, INT_MAX / 2);
pq.emplace(0, node1);
distance[node1] = 0;
while(!pq.empty()) {
const auto [dist, cur] = pq.top();
pq.pop();
if(visited[cur]) continue;
visited[cur] = 1;
for(const auto & adj: graph[cur]) {
const auto [adj_to, adj_w] = adj;
if(distance[adj_to] > distance[cur] + adj_w) {
distance[adj_to] = distance[cur] + adj_w;
pq.emplace(distance[adj_to], adj_to);
}
}
}
return distance[node2] == INT_MAX / 2 ? -1 : distance[node2];
}
};
/**
* Your Graph object will be instantiated and called as such:
* Graph* obj = new Graph(n, edges);
* obj->addEdge(edge);
* int param_2 = obj->shortestPath(node1,node2);
*/
C++
constexpr int directions[4][2] = {{-1,0},{1,0},{0,-1},{0,1}};
class Solution {
public:
int minTimeToReach(vector<vector<int>>& moveTime) {
// [dist, x, y];
priority_queue<tuple<int,int,int>, vector<tuple<int,int,int>>, greater<tuple<int,int,int>>> pq;
pq.emplace(0, 0, 0);
int rows = moveTime.size(), cols = moveTime[0].size();
vector<vector<int>> distance(rows, vector<int>(cols, INT_MAX / 2));
vector<vector<int>> visited(rows, vector<int>(cols, 0));
distance[0][0] = 0;
while(!pq.empty()) {
const auto [dist, x, y] = pq.top();
pq.pop();
if(visited[x][y]) continue;
visited[x][y] = 1;
for(int i = 0; i < 4; i++) {
int nx = x + directions[i][0], ny = y + directions[i][1];
if(nx >= 0 && nx < rows && ny >= 0 && ny < cols) {
int temp_distance = max(dist, moveTime[nx][ny]) + (x+y) % 2 + 1;
if(temp_distance < distance[nx][ny]) {
distance[nx][ny] = temp_distance;
pq.emplace(temp_distance, nx, ny);
}
}
}
}
return distance[rows - 1][cols - 1];
}
};
跟着 灵神的题单 走感觉有进步的说!
然后折磨アイラちゃん四天只睡了 16 个小时的 PPG 算法也算是成功实现了,并且确实收敛的速度更快,只是为什么稳定性不好呢 QAQ
下午上数据库原理课,听不懂事务系统 🐷 时间戳控制事务法和 2PL 法更是稀里糊涂 🫠 后面暑假再仔细看看吧~反正不考
晚上学了一下 Go,这门语言还是很有属于自己的风范的,感觉有集百家之长的特性,希望我能稍微掌握一下吧~(这也是兴趣的一部份呢)
最后,提前祝大家晚安好梦的说! 🥰 也祝大家期末顺利!
口瓜怎么日记帖被拿下了 🥲 5 月 30 日的日记贴变成垃圾消息被 Akismet ちゃん拿下了 🥺
是不是主包发 leetcode 题目和代码重复性太高被拿下了 🫠

アイラちゃん 我们土塬貌似是这样的🤔不时会出现这种吞帖的情况
感觉可以向站务提出说明?
站务应该会看到的,就不再单独麻烦他们了 🥺
akismet ちゃん好严厉,有坏坏的大姐姐感觉 🤣
アイラちゃん 豪德 🫠
好饿,出去觅食了 🐖
好耶,帖子回来啦 🥰
谢谢站务 😘
有 C/C++ 基础看起一门新的语言还是很快的,只是迫切地需要熟练度
从例子入手后再去仔细地阅读语法,希望这样能够快速入门 Go, 然后尝试 6.824
p.s.现在变成 6.5840 了,待我学会 go+ 有时间后尝试一下 😋
每日一题 还是可以 Dijkstra 的,毕竟 Dijkstra 就是 BFS 中的队列->优化队列而已~
坐标变换让我成为 🐖
C++-1 (Dijkstra)
class Solution {
public:
int n;
int CheckTransfer(int nums, vector<vector<int>> & board) {
int row, col;
if(nums % n == 0) {
row = n - nums / n;
if(row % 2 != n % 2) {
col = n-1;
}
else {
col = 0;
}
} else {
row = n - 1 - nums / n;
if(row % 2 != n % 2) {
col = (nums % n) - 1;
}
else {
col = n - (nums % n);
}
}
// // ** debug **
// cout << nums << " at: (" << row << " ," << col << ")\n";
return board[row][col];
}
int snakesAndLadders(vector<vector<int>>& board) {
// Build Matrix.
n = board.size();
vector<int> distance(n*n+1, INT_MAX / 2);
vector<int> visited(n*n+1, 0);
priority_queue<pair<int,int>, vector<pair<int,int>>, greater<pair<int,int>>> pq;
pq.emplace(0,1);
distance[1] = 0;
while(!pq.empty()) {
// BFS to get the adjacent point.
const auto [dist, cur] = pq.top();
pq.pop();
if(visited[cur]) continue;
visited[cur] = 1;
for(int i = cur + 1; i <= min(cur + 6, n * n); i++) {
int transfer = CheckTransfer(i, board);
int adj = transfer == -1 ? i : transfer;
if(distance[adj] > distance[cur] + 1) {
distance[adj] = distance[cur] + 1;
pq.emplace(distance[adj], adj);
}
}
}
return distance[n * n] == INT_MAX / 2 ? -1 : distance[n * n];
}
};
C++-2 (BFS)
class Solution {
public:
int n;
int CheckTransfer(int nums, vector<vector<int>> & board) {
int row, col;
if(nums % n == 0) {
row = n - nums / n;
if(row % 2 != n % 2) {
col = n-1;
}
else {
col = 0;
}
} else {
row = n - 1 - nums / n;
if(row % 2 != n % 2) {
col = (nums % n) - 1;
}
else {
col = n - (nums % n);
}
}
// // ** debug **
// cout << nums << " at: (" << row << " ," << col << ")\n";
return board[row][col];
}
int snakesAndLadders(vector<vector<int>>& board) {
// Build Matrix.
n = board.size();
vector<int> visited(n*n+1, 0);
queue<pair<int,int>> q;
q.emplace(1, 0);
while(!q.empty()) {
// BFS to get the adjacent point.
const auto [cur, dist] = q.front();
q.pop();
visited[cur] = 1;
for(int i = cur + 1; i <= min(cur + 6, n * n); i++) {
int transfer = CheckTransfer(i, board);
int adj = transfer == -1 ? i : transfer;
if(visited[adj]) continue;
if(adj == n * n) return dist + 1;
visited[adj] = 1;
q.emplace(adj, dist + 1);
}
}
return -1;
}
};
成就 badges

不知不觉三个月了,希望有点进步的说
被 xiong 器裹挟的アイラちゃん

今天是端午节,祝塬友们端午快乐!
也祝 @萨卡兹雇佣兵 生日快乐 🥰
アイラちゃん 谢谢泥喵
(话说我也谢了泥好多次了 🤪)
惊觉一门课下周四有随堂考还有 40% 的分数
天塌了,我说怎么这门课没有大作业,原来是今天跟我说改成随堂考了
好好好 🥺
发挥优良传统,一支笔,一个 graffiti,一个晚上,一个奇迹 🐖
アイラちゃん 集百家之长的特性
好乐,难得见一个不骂的
2025 年 5 月 31 日
今天是五月的最后一天!端午假期快乐!
惊觉一门课下周四有随堂考还有 40% 的分数
天塌了,我说怎么这门课没有大作业,原来是今天跟我说改成随堂考了
前情提要⬆️
于是战复习,复习模型压缩,剪枝,量化策略,复习 TPU 脉动阵列模型,数据流不变方式复用比,amdahl 定理,并行加速比 😴
不过已经复习完一半了!明天就可以复习结束的说!
明天可能要下大暴雨了,真讨厌下雨啊,每次鞋都会湿完,但是还是得来图书馆的,在宿舍只想睡觉就好了 🐖
骑士团和狐狸小队的麻麻们

最后,祝大家晚安好梦~
口瓜,已经下雨了,怎么回事口牙
アイラちゃん 上海怎么天天下雨 ![]()
神秘小维 江淮气旋 + 梅雨=暴雨 🥲

明天的雨势更加猛烈 😵💫

2929. 给小朋友们分糖果 II (1701M)(2025.06.01)
数学题~ 祝大家儿童节快乐 ![]()
Go
func distributeCandies(n int, limit int) int64 {
var ans int64
ans = 0
for i := 0; i <= min(limit, n); i++ {
ans = ans + int64(max(int64(min(limit, n-i)) - int64(max(n-i-limit, 0))+1, 0))
}
return ans
}
C++
class Solution {
public:
long long distributeCandies(int n, int limit) {
long long ans = 0;
for(int i = 0; i <= min(limit, n); i++) {
ans += (long long)max(min(limit, n-i) - max(n-i-limit, 0) + 1, 0);
}
return ans;
}
};
神秘小维 正在宾馆里躲雨 ![]()
此人不语,只是一味地在门里生产废料。
上午复习完了周四的考试,实现了昨天在 神秘小维的赛博小窝 的誓言 ![]()
借你吉言,明天一定一定认真复习 🫡
战论文,但是感觉看的很慢而且很难抓住要点
很想了解其他人是怎么快速看完论文,抓住要点并且学习的。一般一篇论文アイラちゃん总会花上 3-5 天(全力)才能看完......
下午看的论文是 sigcomm'14 很经典的 CONGA,一篇针对数据中心的分布式拥塞感知与负载均衡方案。(只看完了动机和目的。。。 🐷)
部分的要点
CONGA 观察到了 ECMP 在负载均衡方面的缺陷:
- 爆发大流时受限于哈希随机分配,碰撞概率增大导致负载不均衡。
- 仅关注局部信息而未关注到潜在下游通路的拥塞影响。
导致了链路故障情况下即使有冗余也使得成功送达流量下降 40%。
并且,针对其他解决方案的不足:如中心化调度导致的数据中心场景下流量快速变化的反应不足,局部路由特性对全局掌握不佳,基于终端主机的传输层协议增加传输层复杂度并且无法控制绕开内核具有自身协议栈的高性能应用,提出了属于自己的方案:
- 分布式框架,允许在 RTT 尺度下进行拥塞反应。
- 全局拥塞信息控制,leaf-leaf 控制快速传输网络拥塞指标
- 独立于传输层的网络层部署,避免影响终端主机和引入复杂的传输层控制。
需要满足:高效性,传输层独立,不对称性稳定,可增量部署,可以针对 Leaf-spine 拓扑优化。

晚上要复习数学了~论文明天再继续看,CONGA 是怎么设计的。
图来自 X: @ sei_umehara

アイラちゃん 看不懂喵😭
窝是来看美图的喵 🫠
尽力做到每大更一次贴一张图 🥰
アイラちゃん 好的喵 🥰
2025 年 6 月 1 日 星期一 天气:中雨
上午:
https://xjtu.app/t/topic/14653/23
下午:
https://xjtu.app/t/topic/14653/25
晚上:
先结束了一元积分的基础复习。先把知识点和基础题都过一遍就好。
数学复习到了多元函数微分,最为头疼的还是多元函数连续/可导/可微/存在极限的关系,还是要记一下这张图 😵💫

祝各位晚安,好梦~ 🥰
X: @Tenkaichiho0

アイラちゃん lz 好自律🥹
长恨此身非我有 何时忘却营营
紫金港第一皮革<・)))><< 大一大二一点不自律 🥲现在只是为了抱佛脚,看看能不能继续上学 😭
其实我更想打 MC,每天 🐖,但是边缘人太折磨了,又不想太早出来工作,想读硕士后再工作
アイラちゃん 话说 LZ 有 paper 的 readlist 吗,还是组里相关的任务什么的
Zerick 是暑期打算在实验室实践(必修,可以走企业实践也可以在学校实验室实践),偏网络仿真方向(开发),目前是学长给了“负载均衡”的相关文献调研,所以读论文,后面还要上 ns3 开发 😇
アイラちゃん 每天 🐖
我也想做一只快乐的皮革兔🥹
长恨此身非我有 何时忘却营营
紫金港第一皮革<・)))><< 其实有好多想做的事
想自驾游从上海回家(海南海口),想学日语考过 N2 然后出国玩一下,想要自驾游从四川到西藏,有很多想做的事,如果有机会一定会好好玩一玩 😋
高考结束后的那个暑假几乎完全没有了,后面每个暑假都被小学期所占据,没有多少时间玩,有点可惜 🥲
悟已往之不谏,知来者之可追
アイラちゃん 很想了解其他人是怎么快速看完论文,抓住要点并且学习的。
+1,我现在非常容易陷入到实现里出不来,但是不看实现又 get 不到 paper 在干什么。
感觉还是读得太少了不知道学界现状,缺少 taste(x
Zerick 对的,可能是需要积累吧。现在总是有很多陌生的术语/词汇,所以阅读的时候磕磕绊绊,但是读多了积累多了应该就快一些了 ![]()
アイラちゃん 想自驾游从上海回家(海南海口),想学日语考过 N2 然后出国玩一下,想要自驾游从四川到西藏
好厉害啊🥹
希望 lz 有朝一日能实现这些目标吧
长恨此身非我有 何时忘却营营
アイラちゃん 同 😭驻波感觉目前不靠任何辅助阅读的手段就会读的很慢,或者晕乎乎的💦常常是看到一半就忍不住让 GPT 翻译了 + 总结要点
アイラちゃん在 zotero 里加装了 bionic 插件
https://github.com/windingwind/bionic-for-zotero

效果是这样的,会稍微有所提升阅读速度,浏览器アイラちゃん也会在阅读长 blog 的时候启用插件辅助阅读
一般来说长文阅读アイラちゃん很容易丢失上下文信息或者不专注(包括中文),阅读很慢,这样会稍微快一些
アイラちゃん 这样吗,有点意思🤔
(确实感觉怪提神的 ![]()
驻波回去也试试喵
今天的天气很舒服,所以上午偷懒了一个小时才起床 🥲
X:@Gameillust_AI

上午结束!说实话有些遗憾,因为刷 leetcode 解题的速度还是比较慢,需要进一步熟悉知识点 + 提升解题/写代码的速度。
每日一题是经典的贪心!
135. 分发糖果 (H)(2025.06.02)
C++
class Solution {
public:
int candy(vector<int>& ratings) {
// Two times iteration.
vector<int> ans;
int count = 1;
ans.emplace_back(count++);
for(int i = 1; i < ratings.size(); i++) {
if(ratings[i] > ratings[i-1]) {
ans.emplace_back(count++);
}
else {
count = 1;
ans.emplace_back(count++);
}
}
#ifdef DEBUG
cout << ans.size() << ' ' << ratings.size() << '\n';
for(int i = 0; i < ans.size(); i++) {
cout << ans[i] << ' ';
}
cout << '\n';
#endif
count = 1;
for(int i = ratings.size() - 1; i > 0; i--) {
if(ratings[i-1] > ratings[i]) {
ans[i-1] = max(ans[i-1], ++count);
}
else {
count = 1;
ans[i-1] = max(ans[i-1], count);
}
}
#ifdef DEBUG
cout << ans.size() << ' ' << ratings.size() << '\n';
for(int i = 0; i < ans.size(); i++) {
cout << ans[i] << ' ';
}
cout << '\n';
#endif
return reduce(ans.begin(), ans.end());
}
};
Go
func candy(ratings []int) int {
n := int(len(ratings))
ans := make([]int, n)
count := 1
ans[0] = count
count++
for i := 1; i < n; i++ {
if ratings[i-1] < ratings[i] {
ans[i] = count
count++
} else {
count = 1
ans[i] = count
count++
}
}
count = 1
for i := n - 1; i > 0; i-- {
if ratings[i-1] > ratings[i] {
count++
ans[i-1] = max(ans[i-1], count)
} else {
count = 1
}
}
var final int = 0
for i := 0; i < n; i++ {
final = final + ans[i]
}
return final
}
继续复习图论:这是图论中经典的拓扑排序,采用 Kahn 算法。
https://oiwiki.com/graph/topo/#kahn-算法
C++
好麻烦的题目
class Solution {
public:
bool isPrintable(vector<vector<int>>& targetGrid) {
// The tuple has color -> (up, down, left, right).
unordered_map<int, tuple<int,int,int,int>> color_mat;
// Find the tuple mat from the graph.
for(int i = 0; i < targetGrid.size(); i++) {
for(int j = 0; j < targetGrid[i].size(); j++) {
int color = targetGrid[i][j];
if(color_mat.find(color) == color_mat.end()) {
color_mat[color] = make_tuple(INT_MAX / 2, 0, INT_MAX / 2, 0);
}
const auto & [up, down, left, right] = color_mat[color];
color_mat[color] = make_tuple(min(up, i), max(down, i), min(left, j), max(right, j));
}
}
// Search the matrix and find the topology order.
unordered_map<int, unordered_set<int>> graph;
unordered_map<int, int> inDegree;
for(const auto & ele: color_mat) {
const int & color = ele.first;
const auto & [up, down, left, right] = ele.second;
graph[color] = unordered_set<int>();
if(inDegree.find(color) == inDegree.end()) {
inDegree[color] = 0;
}
for(int i = up; i <= down; i++) {
for(int j = left; j <= right; j++) {
if(targetGrid[i][j] != color && graph[color].find(targetGrid[i][j]) == graph[color].end()) {
graph[color].insert(targetGrid[i][j]);
inDegree[targetGrid[i][j]]++;
}
}
}
}
int n = graph.size();
queue<int> q;
int count = 0;
for(const auto & ele: inDegree) {
if(ele.second == 0) {
q.emplace(ele.first);
}
}
while(!q.empty()) {
int cur = q.front();
q.pop();
count++;
for(const auto & adj: graph[cur]) {
if(--inDegree[adj] == 0) {
q.emplace(adj);
}
}
}
#ifdef DEBUG
for(const auto & element: color_mat) {
const auto & [u,d,l,r] = color_mat[element.first];
cout << element.first << ":(" << u << ',' << d << ',' << l << ',' << r << ")\n";
}
for(const auto & element: graph) {
cout << element.first << " -> (";
for(const auto & adj: element.second) {
cout << adj << ',';
}
cout << '\n';
}
for(const auto & element: inDegree) {
cout << element.first << ": " << element.second << '\n';
}
cout << count << ' ' << n << '\n';
#endif
return count == n;
}
};
下午要和同学做星期四上午的 pre,以及交换星期二的数据挖掘推荐系统的展示 demo。看来今天下午又无法读论文了......没有意义的大作业真的害人不浅,强烈抨击金课! 😤 先去 🐷啦~
🐖

アイラちゃん 自律 🏃
i-BuProfen 再不跑就生锈了 🥲
现在又下雨了完蛋了 😇
アイラちゃん 好自律
对我来说 跑步一直是极度痛苦的事情😖
长恨此身非我有 何时忘却营营
アイラちゃん 既不想上学也不想上班 我也想每天 🐖
想到将来还要至少上 20 年班我就不行了😭
此人不语,只是一味地在门里生产废料。
post deleted by author 我也不想当社畜 😭
post deleted by author 为什么才 20 年
鳳 笑夢 三十五岁危机 ![]()
此人不语,只是一味地在门里生产废料。
2025 年 6 月 2 日 星期一 天气:小雨转阴
上午:
https://xjtu.app/t/topic/14653/43
下午和晚上:和同学完成星期四 ppt 的制作,以及星期二 demo 的制作。但是没想到我的 baseline 数据竟然不见了!平台的监控数据只保存 14 天,所以今晚要赶紧取得数据补充好我们的 PPT。PPT 上涉及到了模型、算法改动后与 baseline 的对比,虽然效果好像都没有比 baseline 好,甚至比 baseline 差,但还是要展示出来,分析出来效果不好的原因。但是同学训练的 reward 版本很好。
晚上一直在处理数据和 PPT,一点也不想搞了,好累口牙~~~
https://xjtu.app/t/topic/14653/44
希望能坚持自律~不然身体会垮掉的 😥
明天又要上课了,不想上课,不想工作 🥲 只想快乐地 🐷~
祝大家有美好的新的一周!晚安~
X: @ComikeMaster

坚持每日一题是我的 leetcode 账户名,所以就算今天上午 pre,我也要完成它!
(小声:其实做过了)
1298. 你能从盒子里获得的最大糖果数 (1825H)(2025.06.03)
C++
class Solution {
public:
int maxCandies(vector<int>& status, vector<int>& candies, vector<vector<int>>& keys, vector<vector<int>>& containedBoxes, vector<int>& initialBoxes) {
// 2 times bfs. First get all the boxes final status. Second get the candies.
queue<int> q;
for(const auto & init: initialBoxes) {
q.emplace(init);
}
while(!q.empty()) {
int cur = q.front();
q.pop();
if(status[cur] == 1) {
for(const auto & key: keys[cur]) {
status[key] = 1;
}
}
for(const auto & adj: containedBoxes[cur]) {
q.emplace(adj);
}
}
// Then get the boxes candies.
int ans = 0;
for(const auto & init: initialBoxes) {
q.emplace(init);
}
while(!q.empty()) {
int cur = q.front();
q.pop();
if(status[cur] == 1) {
ans += candies[cur];
}
for(const auto & adj: containedBoxes[cur]) {
q.emplace(adj);
}
}
return ans;
}
};
下午先战数据库作业,然后准备后天 pre 的材料 + 再看看做好的复习笔记准备后天下午的考试!
祝自己一切顺利!
X: @kasuga_iz

感觉好像什么都在坏掉
今天感觉心脏有点难受,一个月前买的 pad 的充电线坏了,Mac 的充电线也坏了,type-C aux 转接线也坏了,跑步的鞋也开裂了,今早书包也坏了一个口子出来(看错了,书包是好的 🤣)
只能重新买了吗,但是开销挺大的 🥲
アイラちゃん 那很坏了 🤣
🫳
战数据库事务处理 😤
晚上再补齐 OCC( ✅️ )和 MVCC ⭕️ 🐷
X:@haimiya_neno

事务处理与并发控制
事务基础与并发控制
事务
- 事务 (Transaction) 是什么?
- 是一组数据库运算的集合,可以抽象成一个的不可分的逻辑工作单元
ACID 特性
- 事务具有 ACID 特性
- Atomicity: 原子性,即不可分割。事务要么完全执行,要么完全不执行。
- Consistency: 事务必须保持数据库系统满足持续性,也就是满足数据库系统中的原先拥有的关系和条件。
- Isolation: 隔离性,每个事务都是独立运行,相当于仅仅在单独运行自己。
- Durability: 持续性。事务必须可以从错误中恢复。
并发控制
- 什么是并发控制?并发控制的目的是什么?
- 并发控制是数据库系统保证多个事务运算正确交叠的机制。目的是为了保证事务之间的隔离性,同时增加数据库处理速度,提高吞吐率。
保证正确的交叠:从序列化执行开始
为了简化调度方式,将数据库内的所有操作都看做仅有对特定资源的读取和写入操作。我们用 表示第 i 个事务读取数据 A, 表示第 i 个事务写入数据 A。
序列化调度与等价调度
- 没有任何交叠操作的调度 -> 序列化调度。即完成一个事务的所有操作才去执行下一个事务的操作。
- 等价调度是一组执行效果相同的调度的集合。
可以序列化的调度
- 具有交叠操作的调度等价于某些事务的序列化操作。
- 每个可序列化的调度都可以确保持续性的时候,我们的序列化调度是可持续的。
- 需要我们判断调度是否可序列化。
判断调度可序列化
矛盾操作
- 一对矛盾的操作将具有:
- 不同的事务面向同一份资源。
- 至少有一个操作是写操作。
- 这样我们有:(R1W2 不可重复读),(W1R2 脏页读),(W1W2 更新丢失) 均为矛盾操作。(目前不考虑假性读取和写入偏移)
可序列化的不同层级
-
冲突可序列 (confilict serializability)
- 两个调度是冲突等价的,当且仅当
- 拥有相同事务且事物内部操作相同。
- 两个调度中冲突操作的顺序是相同的。也就是说如果调度一中是 (R1W2),那么调度二也必须保证 R1 在 W2 之前。
- 调度 是冲突可序列的,当它
- 在冲突操作上的顺序与某些序列化调度相同。也就是说,你可以将他转换成序列化调度。
- 两个调度是冲突等价的,当且仅当
-
视图可序列 (view serializability)
- 两个调度是视图等价的当它们
- 事务 1 在调度 1 中读取的初始值和在调度 2 中读取的初始值相同。
- 事务 1 在调度 1 中读取到事务 i 写入的值,则在调度 2 中也应该读取到事务 i 写入的值。
- 所有的最终结果都必须由一个事务来完成。
- 两个调度是视图等价的当它们
-
很显然冲突可序列化是比视图可序列化更严格 的。
前序图
- 判断是否冲突可序列化的简单的方法。
- 每个事务作为一个节点。每个冲突操作对作为一条边。
- 当前序图是有向无环图时,恭喜你它是冲突可序列化的。
并发控制方法
为了保证得到正确的交叠操作调度,我们需要一些并发控制方法。
锁的类型(其实是读写锁换皮)
- 共享锁:用于读操作的共享锁。
- 排它锁:用于写操作的排它锁。
| 可执行判断 | 共享锁 | 排它锁 |
|---|---|---|
| 共享锁 | ✅️ | ❌️ |
| 排它锁 | ❌️ | ❌️ |
简单的排它锁和共享锁无法保证可序列性。
二阶段锁
-
阶段一:获得锁
- 每个事务获取他所需要的锁。
- 可能被拒绝或允许,但是在该阶段不允许有释放锁的行为。
-
阶段二:释放锁
- 每个事务释放它所需要的锁。
- 不允许任何事务获得锁。
- 严格的二阶段锁:在所有事务结束后才能统一释放锁。可以解决脏页读问题。
- 二阶段锁可能导致死锁。通过检测和预防来避免死锁 (银行家算法等...)
- 这里不探索如何解决死锁。
-
一个调度是严格的当一个事务写入一份数据后,其他事务不能读取或覆盖这份数据,直到该事务终结。
-
可以看出严格程度:严格序列化 > 严格二阶段锁 > 冲突可序列化 > 视图可序列化
时间戳排序控制 (T/O)
- 采用时间戳来决定序列化调度中事务执行的顺序。当 TS(T1)<TS(T2) 的时候,执行顺序必须为 T1 -> T2.
- 基础时间戳
- 事务自身将拥有时间戳;TS( )
- 每个数据对象也将有自己的时间戳。
- 数据 X 的读时间戳:R(X)
- 数据 X 的写时间戳:W(X)
基础时间戳控制
-
读操作
- 当 TS(T) < W(X) 的时候,回滚并重新开始,赋予新的 TS(T).
- 否则持续执行并且 .
- 允许拷贝一份 X 来保证重复读取。
-
写操作
- 当 TS(T) < W(X) 或者 TS(T) < R(X) 的时候,回滚并重新开始,赋予新的 TS(T).
- 否则持续执行并且
-
基础的时间戳操作保证了冲突可序列性质。
托马斯写 + 时间戳控制
-
写操作处有所不同。
- 当 TS(T) < R(X) 的时候,回滚。
- 当 TS(T) < W(X) 的时候,忽略写操作,继续执行(因为很快时间戳后面的事务的写操作将覆盖本次的写操作)。
- 否则持续执行并且
-
很明显仅保证视图可序列性。
OCC(乐观并发控制)
- 时间戳协议。
- DBMS 为每一个事务创建了一个私有的工作空间。
- 每个被读取的对象将被复制到工作空间中,对对象的编辑也将加入到工作空间中。
- 当事务提交时,DBMS 将会对比工作空间写集合,每个事务的写空间相同时,装载到全局数据库中。
三阶段
- Read,读取。跟踪每个事务的读、写集合,储存他们的写入对象到私有的工作空间中。DBMS 将会复制一个事务中所有需要的元组到共享数据库中保证可重复读。
- Validation,验证。给每一个事务独有的时间戳,检查他们是否于其他对象相互冲突。
- 当事务唤起提交时,进入验证部分。
- 前向验证:检查正在提交中的事务读/写集合是否与活动中的未提交事务相互重叠。
- 检查时间戳。当提交事务时间戳小于其他活动事务时,需要满足三个条件:
- (1) 其他事务读阶段位于当前事务写阶段完成之后。R2(A)>W1(A)
- (2) 其他事务写阶段在当前事务写阶段完成之后。W2(A)>W1(A), 并且 当前事务没有修改任何其他活动事务要读取的对象。
- (3) 其他事务读阶段在当前事务读阶段完成之后。R2(A)>R1(A), 并且当前事务不修改任何T2 将会读取或写入的对象。
- 反向验证:检查正在提交中的事务读/写集合是否与活动中的已提交事务相互重叠。仍然遵循三条原则。
- 检查时间戳。当提交事务时间戳小于其他活动事务时,需要满足三个条件:
- Write,写。当验证成功时,对所有已经编辑过的对象设置写时间戳,装载他们进入全局数据空间。否则回滚,中断事务。
2025 年 6 月 3 日 星期二 天气:多云
上午:
Demo,以及
https://xjtu.app/t/topic/14653/53
下午和晚上:
https://xjtu.app/t/topic/14653/56
不过没太看明白 MVCC,明天补上来 Note,再 copy 到 blog 上~
星期五又要组会,要炸了,アイラちゃん还没看完 reading list,明天一整天要加油看完 🥲
祝大家好梦~晚安 nya
X:@OuToTmeM

忙晕了,提前更一下日记楼~
2025 年 6 月 4 日 星期三 天气:多云
上午修改明天准备汇报的 ppt,评测结果出来了,强化学习对抗实在是太折磨人了,Level3 的胜率才 25%,不过 Level2 和 Level1 我们的胜率都是 100%。还是可以的吧。
其实做了很多的尝试,更换模型和算法,但是很显然这是不正确的。对抗性最重要的就是 reward 设计,前面的部分应该是在 reward 设计完了后再进行的工作。战略上的失误使得我们还是失去了一些优势。果然盲目的勤奋是没有办法掩盖战略上的懒惰的。这一点需要深思。
下午抽时间 leetcode 保持手感。
C++
class Solution {
public:
string lastSubstring(string s) {
int i = 0, j = 1, n = s.size();
while (j < n) {
int k = 0;
while (j + k < n && s[i + k] == s[j + k]) {
k++;
}
if (j + k < n && s[i + k] < s[j + k]) {
int t = i;
i = j;
j = max(j + 1, t + k + 1);
} else {
j = j + k + 1;
}
}
return s.substr(i, n - i);
}
string answerString(string word, int numFriends) {
if(numFriends == 1) {
return word;
}
string last = lastSubstring(word);
int n = word.size(), m = last.size();
return last.substr(0, min(m, n - numFriends + 1));
}
};
C++
#define DEBUG
#undef DEBUG
class Solution {
public:
string alienOrder(vector<string>& words) {
unordered_map<char, unordered_set<char>> graph;
unordered_map<char, int> inDegree;
bool flag = false;
auto add_edge = [&](const string & l, const string & r) -> void {
int i = 0;
for(i = 0; i < min(l.length(), r.length()); i++) {
if(graph.find(l[i]) == graph.end()) {
graph[l[i]] = unordered_set<char>();
}
if(inDegree.find(l[i]) == inDegree.end()) {
inDegree[l[i]] = 0;
}
if(l[i] != r[i] && graph[l[i]].find(r[i]) == graph[l[i]].end()) {
flag = true;
graph[l[i]].insert(r[i]);
inDegree[r[i]]++;
break;
}
else if(l[i] != r[i]) {
flag = true;
break;
}
}
if(!flag && l.length() > r.length()) {
return;
}
flag = true;
int temp = i;
for(; i < l.length(); i++) {
if(graph.find(l[i]) == graph.end()) {
graph[l[i]] = unordered_set<char>();
}
if(inDegree.find(l[i]) == inDegree.end()) {
inDegree[l[i]] = 0;
}
}
i = temp;
for(; i < r.length(); i++) {
if(graph.find(r[i]) == graph.end()) {
graph[r[i]] = unordered_set<char>();
}
if(inDegree.find(r[i]) == inDegree.end()) {
inDegree[r[i]] = 0;
}
}
};
// Step 1: Add edges.
if(words.size() == 1) {
flag = true;
for(int i = 0; i < words[0].length(); i++) {
if(graph.find(words[0][i]) == graph.end()) {
graph[words[0][i]] = unordered_set<char>();
}
if(inDegree.find(words[0][i]) == inDegree.end()) {
inDegree[words[0][i]] = 0;
}
}
}
else {
for(int i = 0; i < words.size(); i++) {
for(int j = i + 1; j < words.size(); j++) {
add_edge(words[i], words[j]);
}
}
}
#ifdef DEBUG
for(auto & elem: graph) {
cout << elem.first << ": ";
for(const auto & adj: elem.second) {
cout << adj << ' ';
}
cout << '\n';
}
for(auto & elem: inDegree) {
cout << elem.first << ": " << elem.second << "\n";
}
string flag_str = (flag == true) ? "true" : "false";
cout << flag_str << '\n';
#endif
// Step 2: TopoSort.
queue<char> q;
string ans;
for(const auto & ch: inDegree) {
if(ch.second == 0) {
q.emplace(ch.first);
}
}
while(!q.empty()) {
char ch = q.front();
ans.push_back(ch);
q.pop();
for(const auto & adj: graph[ch]) {
if(--inDegree[adj] == 0) {
q.emplace(adj);
}
}
}
return ans.length() == graph.size() && flag ? ans : "";
}
};
C++
基于基环树的思想。
class Solution {
public:
int maximumInvitations(vector<int>& favorite) {
int n = favorite.size();
vector<vector<int>> reverse_graph(n);
queue<int> q;
vector<int> inDegree(n, 0);
for(int i = 0; i < n; i++) {
inDegree[favorite[i]]++;
}
for(int i = 0; i < n; i++) {
if(inDegree[i] == 0) {
q.emplace(i);
}
}
while(!q.empty()) {
int cur = q.front();
q.pop();
// Build reverse_graph;
reverse_graph[favorite[cur]].emplace_back(cur);
if(--inDegree[favorite[cur]] == 0) {
q.emplace(favorite[cur]);
}
}
// Dfs reverse graph find the shortest edge.
auto dfs = [&](this auto && dfs, int from) -> int {
int max_depth = 1;
for(const auto & adj: reverse_graph[from]) {
max_depth = max(max_depth, dfs(adj) + 1);
}
return max_depth;
};
// Find the cycle.
int ring = 0, chain = 0;
for(int i = 0; i < n; i++) {
if(inDegree[i] == 0) {
continue;
}
inDegree[i] = 0;
int tmp_ring = 1;
for(int adj = favorite[i]; adj != i; adj = favorite[adj]) {
inDegree[adj] = 0; // mark as visited.
tmp_ring++;
}
if(tmp_ring == 2) {
chain = chain + dfs(i) + dfs(favorite[i]);
} else {
ring = max(ring, tmp_ring);
}
}
return max(chain, ring);
}
};
然后焦虑/抑郁状态出现了,突然一下子什么都不想干,只能停下手中的学习任务去外面走一走 🥲
拍照的地方在兰香湖附近,是アイラちゃん心情不好的时候最常去的地方。只要在安静的地方发上半个小时的呆,好像什么都会变好的。有时候止不住地想,是不是我选择了错误的道路,选择了错误的人生,还是什么时候做了错误的事情呢,以至于现在进退两难,也不知道未来在哪里,为了尝试保研也没有认真的去找工作。如果,如果考研和保研都失败了,没有实习经验的アイラちゃん又应该怎样才能找到理想的工作呢,如果不找本专业的工作我又会什么呢。现在的アイラちゃん还不知道答案是什么,未来在何处。



今天晚上就是准备明天下午的考试了,希望能够一切顺利吧,明天上午的 pre,明天下午的考试。星期五的组会明天考完试再准备吧。
祝大家今晚好梦~
X: @ji10me

アイラちゃん 现在的アイラちゃん还不知道答案是什么,未来在何处。
我也是😭
此人不语,只是一味地在门里生产废料。
アイラちゃん 如果,如果考研和保研都失败了,没有实习经验的アイラちゃん又应该怎样才能找到理想的工作呢,如果不找本专业的工作我又会什么呢。
答案是并不会怎么样 😁 你想太多了
IO 确实,这种东西太看时代和机遇了。坏时代诺奖得主也要变卖奖牌维持生计,国内院士扫大街;好时代没读过书也能有机会成为顶流网红,带货千万。
2025 年 6 月 5 日 星期四 天气:多云
心情很不好呀。
同一时期投简历的同学今天都收到了面试邀请然后进行了面试,但是我连面试通知都没有收到。(是学校统一安排的专业暑期实习)
虽然打算在学校内实验室进行实习,但是知道自己甚至连面试条件都不准入还是有些伤心的。可能自己的水平确实不够吧,自己平庸的成绩和平庸的表现也确实很难入别人的法眼。接下来还是应该默默地继续努力准备考研,在收留我的实验室组里做一些工作,多学习一些吧。
实力才是硬道理。没有成绩,没有项目傍身的我属实单薄无力。我的喜欢和热爱可能不会成为我最后选择的道路吧,有可能我会在未来放弃计算机,放弃 coding 吧,当真正没有办法的时候。
不过目前アイラちゃん不会那么轻易放弃的。至少让我丑陋地挣扎一下吧,为了一点微不足道的可能。
今天上午的题目是经典的并查集。只需要按照字典序进行 merge 即可。
C++
// #define debug
class Union {
public:
vector<int> parent;
int n;
// The Costruction function.
Union(int n):n(n),parent(vector<int>(n)) {
iota(parent.begin(), parent.end(), 0);
}
// Path compression. Find parent until the same.
int find(int id) {
if(parent[id] != id) {
parent[id] = find(parent[id]);
}
return parent[id];
}
// Merge by size. You can also make it by rank, for modified as size[pa_i] += 1;
void merge(int id1, char id2) {
// Find parent.
int pa1 = find(id1);
int pa2 = find(id2);
if(pa1 == pa2) {
return;
}
else if(pa1 < pa2) {
parent[pa2] = pa1;
}
else {
parent[pa1] = pa2;
}
}
bool judge(int id1, int id2) {
return find(id1) != find(id2);
}
};
class Solution {
public:
string smallestEquivalentString(string s1, string s2, string baseStr) {
Union u(26);
for(int i = 0; i < s1.length(); i++) {
u.merge(s1[i] - 'a', s2[i] - 'a');
}
#ifdef debug
for(auto & element: u.parent) {
cout << element.first << ' ' << element.second << '\n';
}
#endif
string ans;
for(int i = 0; i < baseStr.length(); i++) {
ans.push_back(u.find(baseStr[i] - 'a') + 'a');
}
return ans;
}
};
上午进行了 RL 的 pre,预计明天把报告干出来。
下午考完了试,也意味着期末周已经结束了。
晚上就是不停地赶报告,目前已经写完了更换模型部分 (MLP+LSTM) 更换成 transformer encoder-decoder,transformer+LSTM 部分,以及 dual PPO 和 PPG 的一部分。明天再把剩下的实验结果补充在报告中。只是有些可惜 SAC 的探索失败了,一个分布式框架我不太了解如何将数据序列化传输给训练主机,再将数据反序列化传递回 env,这就导致了我无法完成 off-policy 操作。
今天心情不太好,就不放图了,祝大家未来一帆风顺,今晚做个好梦,晚安。
アイラちゃん 好想大哭一场,但是已经很晚了,会影响到别人休息。大学三年我到底做到了什么呢?学会了什么呢?这三年我是尽力完成了,还是得过且过呢?如果是得过且过,为什么会如此疲惫不堪?如果不是,结果为何不堪入目呢?
无法寻找到答案,也无从知晓答案。不会有人告诉我为什么,因为过程并不重要;成绩❌和暑期实习❌的结果已经说明了一切。
有点累,不想码字了,先睡吧,如果能睡着的话。有可能的话,我好想重新开始一切,平平凡凡地走过人生。或者是有可能的话,重来一次太累,不如一劳永逸地结束吧。
讨厌现在的自己
讨厌现在的生活
早上起床头有些疼,布洛芬又救了我一命。感谢止疼药 💊
然后补完了报告中属于自己的部分。剩下的格式调整和补充等到组内的其他小伙伴完成后再进行修改。这个学期也即将随着夏天的到来而落下帷幕。
然后还是一样先做每日一题。Coding 需要保持手感,否则很快就萎缩了 😵💫
今天是贪心 + 栈。一开始对于贪心的设计不足导致出错,后来发现不能按照单调的方式去想,贪心应该贪心到剩余字符中最小的字符才可以。感觉好像有些晕晕的~
C++
class Solution {
public:
string robotWithString(string s) {
int n = s.length();
vector<char> min_str(n+1);
min_str[n] = 'z';
for(int i = n - 1; i >= 0; i--) {
min_str[i] = min(min_str[i+1], s[i]);
}
stack<char> st;
string ans;
for(int i = 0; i < n; i++) {
st.push(s[i]);
while(!st.empty() && st.top() <= min_str[i+1]) {
ans.push_back(st.top());
st.pop();
}
}
return ans;
}
};
X: @ArameDraw

IO 


今天忙的事情太多啦,明天早上更 🥺
IO 为啥会有人信和传播这些不切实际的胡说八道,为了泄愤罔顾事实?
阿根廷生存压力可比国内大多了,那些踢足球的酒囊饭袋工资比欧洲都高
纯粹是对足球本身没啥兴趣,能捞到钱就躺
2025 年 6 月 6 日 星期五 天气:多云
昨天太乱了把事情都忘了~
上午:https://xjtu.app/t/topic/14653/65
下午上完最后一节数据库课程,大学的所有必修课程也就全部结束了。暑期的实践也只有实验室的项目,其他意向的校外实习全部挂掉了~
随后校内的夏令营开了,就跑来跑去准备材料,但是应该不会报上的吧~其他大学的开放交流日也杳无音讯,一些学院需要的推荐信啥的也什么都没有呢,所以也不需要抱太大的希望。然后晚上 23 点的时候小组同学说报告的格式崩掉了。于是又去想着怎么修格式去了。
目前的计划还是一样按照 https://xjtu.app/t/topic/14653/3 来慢慢推进。一直努力到最后的时刻,之后的路只能且走且看,珍惜自己本科的最后一段时光吧。
愿我们都能获得自己想要的未来。
X: @ArameDraw

fight on なのです!
跑之前没有清鞋里的沙子,痛~

アイラちゃん 随后校内的夏令营开了,就跑来跑去准备材料
我今天刚看到 想着报不上就准备放弃了 把目前在联系的其他学校老师的考核认真做一做
好几天没看 lz 的日记了,感觉我日记没有这么 e 的原因只是单纯的因为土塬有认识我的校友所以不是很愿意彻底在这里解刨自己(笑
总之觉得 lz 已经很优秀了,也很感谢 leetcode 题单的分享
Zerick lz 也是,一边准备夏令营一边准备考研~
一起加油哦,我一直相信大老师说过的话:
努力不会背叛自己,梦想才会。
尽力做好所有的准备后,结果顺其自然。尽人事,听天命就是这个道理。烦恼和伤心,找一个地方记录下来,也算是一种倾泻的方式。
一步步向前走就好了 ![]()
今天可以好好 🐖
顺便奖励自己结束了大三学年

アイラちゃん 盲猜四餐 🐖
神秘小维 等有钱了再去外边好好 🐖 😤
2025 年 6 月 7 日 星期六 天气:小雨
今天考完了语文和数学,语文作文根本不会写,数学大题没做完,没有大学上了 QAQ
上午学习了相关跳表的数据结构,并且完成了基本的跳表操作,以及在整个表层面加入读写锁。对于 C++,我们在表结构内使用 std::shared_mutex 定义读写锁。采用 std::unique_lock 可以完成排他锁加锁工作,也就是写锁。写锁不允许除了持锁线程之外的锁进入到数据结构区域。对于读取操作,我们采用 std::shared_lock 加锁,也就是共享锁。共享锁允许多个仅进行读取操作的线程读入。一般来说是这样的:
简单实例
template<typename T>
class DataStructure {
public:
// Data Definition start
// ********
// Data Definition end
std::shared_mutex rwlock;
template<typename T>
T read() {
std::shared_lock rlock(rwlock); // Read operations with shared_lock;
// Read Operations;
}
template<typename T>
void write() {
std::unique_lock wlock(rwlock); // Write operations with exclusive_lock.
// Write operations;
}
};
对于多线程和 C++ 锁的 RAII 机制,以及原子操作和 CAS 方法还需要进一步地学习和实践。跳表的相关总结也需要在 Jun 8, 2025 (Asia/Shanghai) 尝试完成。
下午就是刷 leetcode。
3170. 删除星号以后字典序最小的字符串 (2025 年 6 月 7 日) (1772M)
这道题目的本质是贪心算法。对于贪心的设计不充分让主包感觉到了非常吃力,后面在图论结束后需要重点进攻 DP 和贪心。(80w vs 60w, 优势在我!)
并且通过这一题还尝试了 ranges 相关函数。
C++
#include<ranges>
class Solution {
public:
string clearStars(string s) {
vector<stack<int>> indices(26, stack<int>());
int count = 0;
for(int i = 0; i < s.length(); i++) {
if(s[i] == '*') {
for(int i = 0; i < 26; i++) {
if(!indices[i].empty()) {
s[indices[i].top()] = '*';
indices[i].pop();
break;
}
}
} else indices[s[i] - 'a'].push(i);
}
return s | ranges::views::filter([](char i) {return i != '*';})
| ranges::to<std::string>();
}
};
接下来就是图论部分:内向基环树。这类数据结构一般出现在每个节点均只有单个出边的有向无环图中,这样每个连通块有且仅有一个环,使得求取最长环成为可能。一般通过拓扑排序去除非环节点后进行。
典型的内向基环树问题。
C++
class Solution {
public:
int longestCycle(vector<int>& edges) {
// TopoSort to modify the graph.
int n = edges.size();
vector<int> inDegree(n, 0);
for(int i = 0; i < n; i++) {
if(edges[i] != -1) inDegree[edges[i]]++;
}
queue<int> q;
for(int i = 0; i < n; i++) {
if(inDegree[i] == 0) {
q.emplace(i);
}
}
while(!q.empty()) {
int cur = q.front();
q.pop();
int adj = edges[cur];
edges[cur] = -1;
if(adj != -1 && --inDegree[adj] == 0) {
q.emplace(adj);
}
}
int ans = -1;
auto dfs = [&](this auto && dfs, int from) -> int {
int nxt = edges[from];
if(nxt == -1) return 0;
edges[from] = -1;
return dfs(nxt) + 1;
};
for(int i = 0; i < n; i++) {
ans = max(ans, dfs(i));
}
return ans == 0 ? -1 : ans;
}
};
与其相反的,另一条最短环问题。对于最短环问题,我们需要逐个(假性)去除边,随后从该边的一个节点开始,进行 Dijkstra 或 floyd 最短路计算出该节点到另一个节点的最短距离,最后的最小环即为 。有向图则以出边为起点进行。具体可以查看:
https://oiwiki.com/graph/min-cycle/
C++
class Solution {
public:
int findShortestCycle(int n, vector<vector<int>>& edges) {
// Build graph.
vector<vector<int>> graph(n);
for(const auto & edge: edges) {
int u = edge[0], v = edge[1];
graph[u].emplace_back(v);
graph[v].emplace_back(u);
}
int * distance = new int [n];
// Dijkstra with delete the edge. For indirect graph, pick one side, run dijkstra.
// Directed should strictly follow start and end point.
auto bfs = [&](int u, int v) -> void {
queue<int> q;
q.emplace(u);
distance[u] = 0;
while(!q.empty()) {
int cur = q.front();
q.pop();
for(const auto & adj: graph[cur]) {
// False delete.
if(cur == u && adj == v) {
continue;
}
// Updated.
else if(distance[adj] < 0x7f7f7f7f) {
continue;
}
else if(distance[adj] > distance[cur] + 1) {
distance[adj] = distance[cur] + 1;
q.emplace(adj);
}
}
}
};
int ans = INT_MAX / 2;
for(const auto & edge: edges) {
memset(distance, 0x7f, sizeof(int) * n);
int u = edge[0], v = edge[1];
bfs(u, v);
ans = min(ans, distance[v] + 1);
}
return ans == INT_MAX / 2 ? -1 : ans;
}
};
最后遇到的 冗余连接 I 和 冗余连接 II 放到 Jun 8, 2025 (Asia/Shanghai) 在进行记录,都涉及到了并查集问题。
随后就是 running+ 🐖
http://xjtu.app/t/topic/14653/70
http://xjtu.app/t/topic/14653/73
晚上被讲解抗日战争的视频硬控了,强力推荐,讲的非常详细,于是偏离主力工作摸鱼~
在此向革命前辈和先烈们致敬,永垂不朽。
https://www.bilibili.com/video/BV1E6TTz9Eqy
今晚就是这样了~祝大家晚安好梦,明天高考继续旗开得胜!
アイラちゃん 有一次面试让手撕读写锁给我干碎了
アイラちゃん
auto dfs = [&](this auto && dfs, int from) -> int {
int nxt = edges[from];
if(nxt == -1) return 0;
edges[from] = -1;
return dfs(nxt) + 1;
};
居然是 cpp23
Zerick 拥抱新标准 🤗
手撕读写锁那很坏了,直接用 C++17 😤
🫨op 的刷题和学习效率都好高
上午算跳表的时间、空间复杂度浪费了好多脑细胞,アイラちゃん是 begg 所以请各路大神写论文的时候说的再详细一些吧 😭
有关跳表总结完成 https://www.cnblogs.com/mumujun12345/p/18919314
每日一题部分:
386. 字典序排数
很经典的 dfs 算法做一次字典序输出。
C++
class Solution {
public:
vector<int> lexicalOrder(int n) {
vector<int> ans;
auto dfs = [&](this auto && dfs, int start) -> void {
if(start > n) {
return;
}
ans.emplace_back(start);
dfs(start * 10);
for(int i = 1; i <= 9; i++) {
if(start + i <= n && (start + i) % 10 != 0) {
ans.emplace_back(start + i);
} else {
break;
}
dfs(10 * (start + i));
}
};
dfs(1);
return ans;
}
};
这论文真是越读越晕,好难理解啊 😵💫
お願い、能不能让 Begg 变聪明一点,我什么都会做的 😭


2025 年 6 月 8 日 星期日 天气:中雨转阴
上午:整理跳表,解决每日一题
http://xjtu.app/t/topic/14653/81
下午:继续看 conga 但是没有看完(只是解决了 conga 里的五个点:为什么分布式,为什么网络层部署,为什么全局感知,为什么小流检测,为什么叶子交换机之间反馈机制)。只能慢慢看了,很复杂,感觉几天都看不完。 😭https://www.cnblogs.com/mumujun12345/p/18906167
晚上发现同学的项目用 makefile 构建,源文件和编译中间文件混杂在一起,就开了一个 pull request,写了一个 cmakefile 来改进编译系统。
是一个很小的 sysY 编译器项目,原来的 makefile 就是寻找 src 内的所有 c,cc 和 cpp,并且头文件和 cpp 文件放在了一起,因此也没有必要去寻找 include 文件夹。直接暴力递归搜索:
file(GLOB_RECURSE SOURCES
"src/*.c"
"src/*.cpp"
"src/*.cc"
)
add_executable(compiler ${SOURCES})
target_include_directories(compiler PUBLIC src)
原来的 makefile 采用 g++ 编译了 c 语言文件,因为我无法得知采用 gcc 编译后是否会出现链接问题,所以强行改变文件的格式,使得 g++ 来进行编译。
# For each C file, get them compile with g++.
foreach(SOURCE ${SOURCES})
if(SOURCE MATCHES "\\.c$")
set_source_files_properties(${SOURCE} PROPERTIES LANGUAGE CXX)
endif()
endforeach()
最后设定头文件搜索文件夹地址,以及编译形成的可执行文件(项目)。
# add executable, the final ./compiler.
add_executable(compiler ${SOURCES})
target_include_directories(compiler PUBLIC src)
最后不要忘记在项目开始设定编译选项,指定 g++,输出 compile_commands.json 给 clangd 做索引分析~
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -Wall")
set(CMAKE_EXPORT_COMPILE_COMMANDS true)
这样就成功地赚到了一个 PR,并被 merge~

新的一周即将开始,祝大家工作、学习顺利,晚安~
X:@PhD_illust

等有了小钱,就租一个小窝就好了
也不是说舍友不好,只是想有一个自己安静的小窝
アイラちゃん 一间自己的房间! ![]()
水源账号消失了 🥺
i-BuProfen 水源带给我的焦虑感和矛盾感比较强,所以暂时销号了 🥺 抱歉
今天打算主攻 leetcode。今天的题目是十叉树,其衍生的字典树还没有学习 😵💫,于是开启 cv 法阵,所以要找时间补题了。
C++
class Solution {
public:
int getSteps(int curr, long n) {
int steps = 0;
long first = curr, last = curr;
while(first <= n) {
steps += min(last,n) - first + 1;
first *= 10;
last = last * 10 + 9;
}
return steps;
}
int findKthNumber(int n, int k) {
int curr = 1;
k--;
while(k > 0) {
int steps = getSteps(curr, n);
if(steps <= k) {
k -= steps;
curr++;
} else {
curr = curr * 10;
k--;
}
}
return curr;
}
};
并查集。并查集的核心点其实就是路径压缩和按秩合并(按大小合并)。两者均可以到达一个较好的时间复杂度,时间复杂度的分析比较复杂,刷 leetcode 以实用为主,就不去分析了。
贴上模板 (按大小合并)~
C++ union template
class Union {
public:
vector<int> parent;
vector<int> size;
int components;
// The Costruction function.
Union(int n):
parent(vector<int>(n)),
size(vector<int>(n,1)),
components(n)
{
iota(parent.begin(), parent.end(), 0);
}
// Path compression. Find parent until the same.
int find(int id) {
if(parent[id] != id) {
parent[id] = find(parent[id]);
}
return parent[id];
}
// Merge by size. You can also make it by rank, for modified as size[pa_i] += 1;
void merge(int id1, int id2) {
// Find parent.
int pa1 = find(id1);
int pa2 = find(id2);
if(pa1 == pa2) {
return;
}
if(size[pa1] < size[pa2]) {
parent[pa1] = pa2;
size[pa2] += size[pa1];
}
else {
parent[pa2] = pa1;
size[pa1] += size[pa2];
}
if(pa1 != pa2)
components--;
}
bool judge(int id1, int id2) {
return find(id1) != find(id2);
}
};
可以用来计算连通分量。以下是前面挖的坑,今天补上。
无向图只需要对边进行遍历,判断这条边会不会把位于同一个并查集的边连接起来,如果出现则直接返回。
想到了什么?对的 Kruskal 算法也是这么做的,生成最小生成树,首先贪心地选择边权最小的边,判断两端点是否在同一个并查集中。
C++
inline int find(vector<int> & parent,int id) {
if(id != parent[id]) {
return find(parent,parent[id]);
}
return parent[id];
}
inline void merge(vector<int> & parent,vector<int> & size, int id1, int id2) {
int pa1 = find(parent,id1);
int pa2 = find(parent,id2);
if(pa1 == pa2) {
return;
}
// small merge to big.
if(size[pa1] < size[pa2]) {
parent[pa1] = pa2;
size[pa1] += size[pa2];
} else {
parent[pa2] = pa1;
size[pa2] += size[pa1];
}
}
inline bool judge(vector<int> & parent,int id1, int id2) {
return find(parent,id1) == find(parent,id2);
}
class Solution {
public:
vector<int> findRedundantConnection(vector<vector<int>>& edges) {
vector<int> parent(static_cast<int>(edges.size() + 1));
iota(parent.begin(), parent.end(), 0);
vector<int> size(static_cast<int>(edges.size() + 1), 1);
vector<int> ans;
for(const auto & edge: edges) {
int u = edge[0], v = edge[1];
if(!judge(parent, u, v)) {
merge(parent, size, u,v);
}
else {
ans={u,v};
break;
}
}
return ans;
}
};
题目有些难,看了题解才知道怎么做。主要的就是寻找汇点(因为每个节点只允许拥有一个父节点)和环路边。当两者均出现的时候,删除环路边。仅有冲突边的时候,删除最后一个冲突边。仅有环路的时候,删除环路最后一条边。针对一个节点不会出现两条以上的冲突边和环路边,因为题目保证了(卡在这里想了很久) 🐷

C++
class Union {
public:
int * ancestors;
int * size;
int n;
public:
Union(int n):n(n) {
ancestors = new int [n];
size = new int [n];
for(int i = 0; i < n; i++) {
ancestors[i] = i;
size[i] = 1;
}
}
int find(int id) {
if(id != ancestors[id]) {
ancestors[id] = find(ancestors[id]);
}
return ancestors[id];
}
void merge(int id1, int id2) {
int pa1 = find(id1);
int pa2 = find(id2);
if(pa1 == pa2) {
return;
} else if(size[pa1] > size[pa2]) {
size[pa1] += size[pa2];
ancestors[pa2] = pa1;
} else {
size[pa2] += size[pa1];
ancestors[pa1] = pa2;
}
}
bool judge(int id1, int id2) {
return find(id1) == find(id2);
}
};
class Solution {
public:
vector<int> findRedundantDirectedConnection(vector<vector<int>>& edges) {
int n = edges.size();
Union un(n+1);
int conflict = -1; // Mark conflict apperance.
int loop = -1;
int * parents = new int [n+1];
for(int i = 0; i < n + 1; i++) {
parents[i] = i;
}
// Two parent: Conflict.
// Same ancestor
for(int i = 0; i < n; i++) {
int u = edges[i][0], v = edges[i][1];
if(parents[v] != v) {
conflict = i;
} else {
parents[v] = u;
int pa_u = un.find(u);
int pa_v = un.find(v);
if(pa_u == pa_v) {
loop = i;
} else {
un.merge(u, v);
}
}
}
if(conflict == -1) {
return edges[loop];
} else {
int u = edges[conflict][0], v = edges[conflict][1];
if(loop == -1) {
return edges[conflict];
} else {
return vector<int>{parents[v], v};
}
}
}
};
X: @kasuga_iz

2025 年 6 月 9 日 星期一 天气:阵雨
上午提要~:http://xjtu.app/t/topic/14653/88
本来想在 leetcode 上一展宏图,没想到被拉去做服务器运维去了 😭 记录一下今天踩的坑 🐷
前情提要
在做好一切 ssh 配置后,进行最后的维护检查,htop 时发现了 64 核 cpu 全部 100% 占用。第一反应是查看运行进程,发现是大量不具名的
--bash进程。
进行断网处理后,发现进程不再继续进行。断开所有 ssh 连接后,重新恢复网络连接,通过 wireshark 发现了一个非常可疑的发往 143.110.58.7 的网络包(实际挖矿 IP 代理有美国,欧洲和中国香港 IP),通过 80 端口发出,随后进行三次握手后,所有 cpu 开始饱满。通过sudo ufw deny 80/tcp后杀死进程不会再重启。否则会在一段时间后重启。立刻怀疑服务器已经被植入挖矿脚本。随后证实了如下情况。
通过日志得知该进程在嗅探过程中发现本机 22 端口完全不设防并且密码极其简单,采用暴力方式破解后通过 ssh 植入了后门。只可惜没有做的太干净,还是被我发现了 😅
服务器的密码设置的太简单,又通过内网穿透暴露到了外网,被端口扫描发现并植入了恶意挖矿脚本,进行全天候 24 小时挖矿,并且夺取了系统的 root 权限。于是我们只得重新安装 bios 和操作系统。
今天师傅重新安装了 BIOS。于是我们重新安装 ubuntu22.04 到服务器上。我们的服务器共两台,每台是 64 核 CPU,x86_64 架构,CPU 是很不错的 AMD Ryzen Threadripper PRO 7975WX 32-Cores,是很先进的 cpu~,cpu 支持 52 位实地址,57 位虚地址。感兴趣的小伙伴可以看看,偷偷打印了一份 CPU 的报告 😋
cpu.txt|attachment (3.6 KB)
然后 GPU 是 L40,两机各两台。通过 nvidia 的 bluefield 的 DPU,支持 InfiniBand,RocE 两种 RDMA 协议,可以让两卡通信数据绕过 cpu 内核直接写入内存。之前还出现过高温的情况(DPU 烧到了接近 100 度 😭),不过后面还是通过加装更多的风扇解决了。
开始处理。首先基本的系统安装完成后,开始装载我们的 7.3T 的硬盘和 NvMe 硬盘。担心原先的硬盘也被动了手脚,于是我重新进行了分区,格式化硬盘里的所有数据,在 root 路径下创建一个新的文件夹,并将其挂载。
mklabel gpt
mkpart primary 1 -1
p # output
sudo mkfs.ext4 /dev/sda # 格式化硬盘
手动挂载会导致服务器重新上电后不自动挂载。因此我们将挂载规则写入 etc/fstab

/dev/sda /disk1 ext4 defaults 0 0
这样每次都会将我们的硬盘以 ext4 格式挂载到/disk1 路径下。
接下来还需要扩展系统盘。默认的系统盘安装在 NvMe 后,只会分配 100GB 的空间。我们将 NvMe 硬盘分成了 3 个分区,前两个分区用于
- /boot(目录用于存储启动 Linux 系统所需的核心文件,包括内核文件(如 vmlinuz),初始 RAM 磁盘映像(如 initrd 或 initramfs),以及用于启动加载程序(如 GRUB)的配置文件);
- /boot/efi(一个可扩展固件接口,用于efi启动器的挂载点,包含了efi版本的grub);
最后一个分区被用来整个作为系统盘。我们于是:
sudo vgdisplay # 查看各个盘,分区和大小
lvextend -l +100%FREE /dev/mapper/ubuntu--vg-ubuntu--lv # 记不清名字啦
resize2fs dev/mapper/ubuntu--vg-ubuntu--lv # 重新调整大小进行扩容
这样就成功扩容系统盘大小。效果图如下:

接下来解决启动过程中等待时间过长问题,经过排查得知是强行等待网络连接直至 2min30s 超时导致,于是手动修改 systemd-networkd-wait-online.service 规则。
cd /etc/systemd/system/network-online.target.wants/
sudo vim systemd-networkd-wait-online.service
在 [Services] 一栏添加 TimeoutSec=5,强制超时 5s 后立刻启动。

这样无需等待启动。
此时另一位同学加入战场,于是失去了学习配置 ssh 公钥私钥的机会,去安装相关 nvidia 驱动,cuda 编译相关工具。
首先安装 C/C++ 相关编译工具链。
sudo apt update && sudo apt install build-essentials
安装 gcc-11 和 g++-11 版本适配 Ubuntu22.04. 接下来安装 nvidia 相关驱动,nvidia-smi 显示相关驱动为 570 版本。
sudo ubuntu-drivers devices
sudo ubuntu-drivers autoinstall
成功安装 nvidia 相关驱动。接着安装相关工具链,这也很简单,也就是sudo apt install nvidia-cuda-toolkit
很顺利,和第一次一样成功安装。

接下来进行 gpu 压力测试,我们采用最经典的 gpu-burn。
wget https://codeload.github.com/wilicc/gpu-burn/zip/master # 下载
unzip gpu-burn-master.zip # 解压缩
cd gpu-burn-master
make
形成 gpu-burn 可执行文件。基于之前的经验,采用 double 运算无法让 GPU 达到功率墙,因为 GPU 最擅长的不是双精度浮点运算,而是单精度 float。因此进行压测,采用 90% 的 memory 占用,压测 5 分钟。五分钟满载测试,GPU 温度不超过 75 度。成功。
./gpu-burn -tc -m 90% 300 # tc 启用 tensorCore

接下来检查 cpu 是否在上次的挖矿中受损。因此采用 stress --cpu 64 对全部 cpu 进行压测。温度在 85 摄氏度左右,表现正常。

此时同学已经将公,私钥配好并进行分发。于是在配置好中央 windows 跳板机后,利用跳板机配置代理转发功能,实现跳板机登录服务器。首先将私钥存入本机中。/User/xxx/.ssh/id_jump 中。随后在 vscode 内写入规则。
Host 这里是服务器名
HostName 这里是 IP
User 这里是用户名
Port 这里是 ssh 端口
ProxyJump 这里是跳板机
IdentityFile /Users/xxx/.ssh/id_jump // 私钥地址
Host 跳板机名
HostName IP
User 用户名
Port 端口
IdentityFile /Users/xxx/.ssh/id_jump // 私钥地址
这样就实现了一键无密钥登录服务器。仅有跳板机位于公网,且安装了强力的杀毒软件(是的,360,以毒攻毒),封锁了 80(HTTP),3389(远程协助),443(HTTPS),22(ssh) 等大量专用端口,并使用公私钥进行 ssh 登录,不让坏人具有任何可乘之机。
于是经过一个下午的忙碌,服务器终于被抢救回了原来的模样。这件事也时刻告诫我:网络安全不是儿戏,要更加认真地担负起责任。
祝大家晚安~
🌃
X: @chun_paretto

アイラちゃん 服务器的密码设置的太简单,又通过内网穿透暴露到了外网,被端口扫描发现并植入了恶意挖矿脚本,进行全天候 24 小时挖矿,并且夺取了系统的 root 权限。
好像校内不少服务器都难逃此劫啊。
PipaQinse233 对的,挖矿行动很猖獗 🥲
アイラちゃん 刚才顺手检查了一下自己的小服务器 (给寝室以及行政班里的 Minecraft 服务器用的),发现 ssh 登陆失败的那几条全是因为自己忘记了密码🤣,估计是因为内网穿透只暴露了俩端口且穿透后只能校内访问,才逃过一劫吧。
不过好像也有校内主机感染木马病毒、进一步扫描爆破其他主机的说法,不确定现在还有没有。
PipaQinse233 还是要做好防护,小服务器不需要担心太多,只需要封锁该封的端口,ssh 尽量走公私钥,像大服务器还要做负载冗余,容灾,博客园最近一直再收到 DDOS 攻击,今晚好像又瘫痪了。阿里云的云服务好像也前几天早上瘫痪过,导致我都看不了我的 blog 了 😇
现在还是 502 bad gateway,经典的超负荷情况,博客园又买不起昂贵的 cdn,血都被 csdn 吸干了
今天中午打算回去打扫一下宿舍,不然真的脏的没救了
今天中午怎么宿舍人都在,以前可不是这样的
等到下午他们走了再打扫不然打扫不干净
アイラちゃん
IO 阿里云是域名劫持吗,这我就不清楚了
博客园昨天确实是 DDOS
向图论大神学习,每天坚持 leetcode!
主治医师李大华 ❌️
并非大神,是抱佛脚而已 🥲
Leetcode 日~
拓扑排序找出环并记录环长度,dfs 记录链表长度并剪枝即可,因为题目帮助我们排除了多出边的情况。
C++
class Solution {
public:
vector<int> countVisitedNodes(vector<int>& edges) {
int n = edges.size();
queue<int> q;
int * inDegree = new int [n];
int * mark = new int [n]; // Mark vertex as loop node = 0 or as chain node. chain = 1.
vector<int> ans(n, 1);
memset(inDegree, 0, sizeof(int) * n);
memset(mark, 0, sizeof(int) * n);
// O(n)
for(int i = 0; i < n; i++) {
inDegree[edges[i]]++;
}
// O(n)
for(int i = 0; i < n; i++) {
if(inDegree[i] == 0) {
q.emplace(i);
}
}
while(!q.empty()) {
int cur = q.front();
mark[cur] = 1;
q.pop();
if(--inDegree[edges[cur]] == 0) {
q.emplace(edges[cur]);
}
}
int loop = 1;
auto dfs = [&](this auto && dfs, int from) -> void {
int next = edges[from];
if(mark[next] == 2 || next == -1) {
mark[from] = 2;
ans[from] = loop;
return;
}
edges[from] = -1;
mark[from] = 2; // visited.
loop++;
dfs(next);
ans[from] = loop;
};
for(int i = 0; i < n; i++) {
if(mark[i] == 1 || mark[i] == 2) continue;
loop = 1;
dfs(i);
}
int start, chain;
auto dfs2 = [&](this auto && dfs, int start) -> void {
int next = edges[start];
if(mark[next] == 2 || edges[next] == -1) {
chain += ans[next];
ans[start] = chain;
mark[start] = 2;
return;
}
mark[start] = 2;
dfs(next);
ans[start] = ++chain;
};
for(int i = 0; i < n; i++) {
if(mark[i] == 2) continue;
start = i;
chain = 1;
dfs2(start);
}
return ans;
}
};
进入 dijkstra 算法。
代表性题目,我们用 C 搓一下轮子。
C
typedef struct Node {
int v;
int w;
struct Node * next;
} Node;
void node_init(Node * obj, int to, int weight) {
obj -> v = to;
obj -> w = weight;
obj -> next = NULL;
}
typedef struct DiGraph {
int n;
Node ** headers;
Node ** tails;
} DiGraph;
void BuildGraph(DiGraph * obj, int n, int ** times, int timesSize) {
obj -> n = n;
obj -> headers = (Node **) malloc(sizeof(Node *) * n);
obj -> tails = (Node **) malloc(sizeof(Node *) * n);
for(int i = 0; i < n; i++) {
obj -> headers[i] = (Node *) malloc(sizeof(Node));
node_init(obj -> headers[i], i, 0);
obj -> tails[i] = obj -> headers[i];
}
for(int i = 0; i < timesSize; i++) {
int u = times[i][0] - 1, v = times[i][1] - 1, w = times[i][2];
obj -> tails[u] -> next = (Node *)malloc(sizeof(Node));
obj -> tails[u] = obj -> tails[u] -> next;
node_init(obj -> tails[u], v, w);
}
}
typedef struct min_heap {
int n;
int size;
Node * node;
} min_heap;
void init_min_heap(min_heap * obj, int n) {
obj -> n = n;
obj -> size = 0;
obj -> node = (Node *) malloc(sizeof(Node) * n);
}
int parent(int i) {
return (i + 1) / 2 - 1 > 0 ? (i + 1) / 2 - 1 : 0;
}
int left_child(int i) {
return i * 2 + 1;
}
int right_child(int i) {
return (i + 1) * 2;
}
void min_heapify(min_heap * obj, int i) {
if(i >= obj -> size) {
return;
}
int lchild = left_child(i);
int rchild = right_child(i);
// swap the smallest up.
int smallest = i;
if(lchild < obj -> size && obj -> node[lchild].w < obj -> node[smallest].w) {
smallest = lchild;
}
else smallest = i;
if(rchild < obj -> size && obj -> node[rchild].w < obj -> node[smallest].w) {
smallest = rchild;
}
if(smallest != i) {
Node temp = obj -> node[i];
obj -> node[i] = obj -> node[smallest];
obj -> node[smallest] = temp;
min_heapify(obj, smallest);
}
}
Node top(min_heap * obj) {
return obj -> node[0];
}
void pop(min_heap * obj) {
if(obj -> size == 0) {
return;
}
Node minimum = obj -> node[0];
obj -> node[0] = obj -> node[obj -> size - 1];
obj -> size--;
min_heapify(obj, 0);
}
void push(min_heap * obj, Node node) {
obj -> size++;
obj -> node[obj -> size - 1] = node;
for(int i = obj -> size - 1; i >= 0; i = parent(i)) {
if(obj -> node[parent(i)].w > obj -> node[i].w) {
Node tmp = obj -> node[parent(i)];
obj -> node[parent(i)] = obj -> node[i];
obj -> node[i] = tmp;
}
else break;
}
}
char empty(min_heap * obj) {
return obj -> size == 0;
}
int networkDelayTime(int** times, int timesSize, int* timesColSize, int n, int k) {
DiGraph * digraph = (DiGraph *) malloc(sizeof(DiGraph));
BuildGraph(digraph, n, times, timesSize);
min_heap * test_heap = (min_heap *) malloc(sizeof(min_heap));
init_min_heap(test_heap, timesSize);
int * visited = (int *) malloc(sizeof(int) * n);
int * distance = (int *) malloc(sizeof(int) * n);
memset(visited, 0, sizeof(int) * n);
memset(distance, 0x7f, sizeof(int) * n);
visited[k - 1] = 1;
distance[k - 1] = 0;
Node cur;
node_init(&cur, k - 1, 0);
push(test_heap, cur);
while(!empty(test_heap)) {
cur = top(test_heap);
pop(test_heap);
int cur_v = cur.v, cur_w = cur.w;
for (Node * adj = digraph -> headers[cur_v]; adj != NULL; adj = adj -> next) {
int adj_v = adj -> v, adj_w = adj -> w;
if(distance[adj_v] > distance[cur_v] + adj_w) {
distance[adj_v] = distance[cur_v] + adj_w;
Node tmp;
node_init(&tmp, adj_v, distance[adj_v]);
push(test_heap, tmp);
}
}
}
int ans = 0;
for(int i = 0; i < n; i++) {
ans = ans < distance[i] ? distance[i] : ans;
}
return (ans == 0x7f7f7f7f) ? -1 : ans;
}
只需要判断更新前最短路是否大于消失时间即可。最后用 ranges 转换一下。
C++
#include<ranges>
class Solution {
public:
vector<int> minimumTime(int n, vector<vector<int>>& edges, vector<int>& disappear) {
vector<vector<pair<int,int>>> graph(n);
for(const auto & edge: edges) {
int u = edge[0], v = edge[1], w = edge[2];
graph[u].emplace_back(v,w);
graph[v].emplace_back(u,w);
}
priority_queue<pair<int,int>, vector<pair<int,int>>, greater<pair<int,int>>> pq;
pq.emplace(0,0);
vector<int> distance(n, INT_MAX);
vector<int> visited(n, 0);
distance[0] = 0;
while(!pq.empty()) {
const auto [dist, cur] = pq.top();
pq.pop();
if(visited[cur]) continue;
visited[cur] = 1;
for(const auto & [adj, w]: graph[cur]) {
if(disappear[adj] > w + distance[cur] && distance[adj] > w + distance[cur]) {
distance[adj] = distance[cur] + w;
pq.emplace(distance[adj], adj);
}
}
}
return distance | ranges::views::transform([](int i){
return (i == INT_MAX) ? -1 : i;
}) | ranges::to<vector<int>>();
}
};
将加法改成乘法。
C++
class Solution {
public:
double maxProbability(int n, vector<vector<int>>& edges, vector<double>& succProb, int start, int end) {
vector<vector<pair<double, int>>> graph(n);
for (int i = 0; i < edges.size(); i++) {
int u = edges[i][0], v = edges[i][1];
double w = succProb[i];
graph[u].emplace_back(w, v);
graph[v].emplace_back(w, u);
}
priority_queue<pair<double, int>> pq;
vector<double> distance(n, 0);
vector<int> visited(n, 0);
pq.emplace(1, start);
distance[start] = 1;
while (!pq.empty()) {
const auto [dist, cur] = pq.top();
pq.pop();
if (visited[cur]) {
continue;
}
visited[cur] = 1;
for (auto& [w, adj] : graph[cur]) {
if (distance[adj] < distance[cur] * w) {
distance[adj] = distance[cur] * w;
pq.emplace(distance[adj], adj);
}
}
}
return distance[end];
}
};
本来这道题不难的,就是 dijkstra+ 图上 DP,DP 的方式也就是
但是进行 dfs 过程中アイラちゃん错误地初始化 dp 数组为 0,最终会导致一部分剪枝失败(当一个点的所有邻接点均不可以 dfs 时,dp[i]=0, 这样下一次遇到就会反复重新进行该点邻接点的遍历),从而导致记忆化搜索失败。寻找这个问题耗费了接近 1h 😵💫
C++
constexpr int MOD = 1e9 + 7;
class Solution {
vector<vector<pair<int,int>>> graph;
vector<int> distance, visited, dp;
int num;
public:
int dfs(int cur) {
if(cur == num) {
return 1;
} else if(dp[cur] != -1) {
return dp[cur];
}
int ans = 0;
for(const auto & adj: graph[cur]) {
int nxt = adj.first;
if(distance[cur] > distance[nxt]) {
ans = (ans + dfs(nxt)) % MOD;
}
}
dp[cur] = ans;
return ans;
}
int countRestrictedPaths(int n, vector<vector<int>>& edges) {
num = n;
graph = vector<vector<pair<int,int>>>(n + 1);
// 1 as start index.
for(const auto & edge: edges) {
int u = edge[0], v = edge[1], w = edge[2];
graph[u].emplace_back(v,w);
graph[v].emplace_back(u,w);
}
priority_queue<pair<int, int>, vector<pair<int,int>>, greater<pair<int,int>>> pq;
pq.emplace(0, n);
distance = vector<int>(n+1, INT_MAX);
visited = vector<int>(n+1, 0);
distance[n] = 0;
// Dijkstra started from last node.
while(!pq.empty()) {
const auto [dist, cur] = pq.top();
pq.pop();
if(visited[cur] == 1) continue;
visited[cur] = 1;
for(const auto & [adj, w]: graph[cur]) {
if(distance[adj] > distance[cur] + w) {
distance[adj] = distance[cur] + w;
pq.emplace(distance[adj], adj);
}
}
}
// Distance to LastNode complete.
dp = vector<int>(n+1, -1);
dp[n] = 1;
return dfs(1);
}
};
X: @fuumiisc

要再接再厉。
良辰吉时已到,宿舍无人。
环境之脏乱已难以忍受,开干!
清扫完成~这宿舍之前真的是狗窝
アイラちゃん 看看干净的宿舍()
长恨此身非我有 何时忘却营营
紫金港第一皮革<・)))><< 宿舍



清除垃圾

有些陈年水垢无法清除了
我从不点外卖,因为太贵了
刚刚洗完澡地也干了
アイラちゃん 你们居然还是独立卫生间吗
是东区吗🤔
神秘小维 📦️
![]()
其实我也拍了清洗前的宿舍,但是有碍国际观瞻,就不放出来了
![]()
アイラちゃん 看到这个 忽然也不羡慕东区独立卫浴了 这个设施看起来好旧啊
以及 lz 的清洁能力好强 (我也想要这样的室友)
长恨此身非我有 何时忘却营营
紫金港第一皮革<・)))><< 不要羡慕,厕所地面高于宿舍,清洗起来很麻烦的 😭
アイラちゃん
这……让我想到了某些不好的画面
下午开会 + 开会 + 摸鱼 😇
2025 年 6 月 11 日 星期三 天气:阴/阵雨
今天除了上午刷了 leetcode,下午就是开组会 + 开专业毕业会 + 开夏令营宣讲会,合理摸鱼日~
🐟️
今天的题目都稍微比较难,为了调超时都很难受 😵💫
这是一道前缀和 + 滑动窗口题目,アイラちゃん一直很不懂怎么很好地写双指针,因此每次都调试很久+cv 🥺
C++
class Solution {
public:
int maxDifference(string s, int k) {
const int inf = INT_MAX / 2;
int ans = -inf;
for (int x = 0; x < 5; x++) {
for (int y = 0; y < 5; y++) {
if (y == x) {
continue;
}
int cur_s[5]{}, pre_s[5]{};
int min_s[2][2] = {{inf, inf}, {inf, inf}};
int left = 0;
for (int i = 0; i < s.size(); i++) {
cur_s[s[i] - '0']++;
int r = i + 1;
while (r - left >= k && cur_s[x] > pre_s[x] && cur_s[y] > pre_s[y]) {
int& p = min_s[pre_s[x] & 1][pre_s[y] & 1];
p = min(p, pre_s[x] - pre_s[y]);
pre_s[s[left] - '0']++;
left++;
}
ans = max(ans, cur_s[x] - cur_s[y] - min_s[cur_s[x] & 1 ^ 1][cur_s[y] & 1]);
}
}
}
return ans;
}
};
直接走 dijkstra+dfs 将会超时。因为可以构造一个长链连接到最后的全连接图上面,全连接图的 dfs 遍历将出现指数级复杂度,从而超时。
当然也可以用 dijkstra 算法,从 0 计算一次,再从 n-1 计算一次,最后判断 是否成立就可以了。
C++
using namespace std;
using pii = pair<int,int>;
using tiii = tuple<int,int,int>;
using Graph = vector<vector<tiii>>;
#define RELEASE
class Solution {
public:
vector<bool> findAnswer(int n, vector<vector<int>>& edges) {
// Build graph here.
Graph graph(n);
int ind = 0;
for(const auto & edge: edges) {
int u = edge[0], v = edge[1], w = edge[2];
graph[u].emplace_back(v,w,ind);
graph[v].emplace_back(u,w,ind);
ind++;
}
// iterate the graph now.
vector<int> distance(n, INT_MAX / 2);
vector<int> visited(n, 0);
priority_queue<pii, vector<pii>, greater<pii>> pq;
pq.emplace(0,0);
vector<vector<int>> pre(n); // Record the pre vertex of the graph.
distance[0] = 0;
while(!pq.empty()) {
const auto [dist, cur] = pq.top();
pq.pop();
if(visited[cur] == 1) continue;
visited[cur] = 1;
// Iterate over the adjacent vertices.
for(const auto & [adj, w, _]: graph[cur]) {
if(distance[adj] > dist + w) {
distance[adj] = distance[cur] + w;
pq.emplace(distance[adj], adj);
}
}
}
vector<char> visited2(n, 0);
vector<bool> ans(edges.size(), false);
auto dfs = [&](this auto && dfs, int from) -> void {
visited2[from] = 1;
for(const auto & [adj, w, index]: graph[from]) {
if(distance[adj] + w != distance[from]) {
continue;
}
ans[index] = true;
if(!visited2[adj]) {
dfs(adj);
}
}
};
if(distance[n-1] == INT_MAX / 2) return ans;
dfs(n-1);
return ans;
}
};
同样 dijkstra+dfs,稍微更改一下上面的就可以了。
要记得开 long long
C++
using plli = pair<long long,int>;
using Graph = vector<vector<pair<int,int>>>;
using ll = long long;
constexpr int MOD = 1e9+7;
#define RELEASE
class Solution {
public:
int countPaths(int n, vector<vector<int>>& roads) {
// Build graph here.
Graph graph(n);
for(const auto & edge: roads) {
int u = edge[0], v = edge[1], w = edge[2];
graph[u].emplace_back(v,w);
graph[v].emplace_back(u,w);
}
// iterate the graph now.
vector<ll> distance(n, LLONG_MAX);
vector<int> visited(n, 0);
priority_queue<plli, vector<plli>, greater<plli>> pq;
pq.emplace(0,0);
distance[0] = 0;
while(!pq.empty()) {
const auto [dist, cur] = pq.top();
pq.pop();
if(visited[cur] == 1) continue;
visited[cur] = 1;
// Iterate over the adjacent vertices.
for(const auto & [adj, w]: graph[cur]) {
if(distance[adj] > (ll)dist + w) {
distance[adj] = (ll)distance[cur] + w;
pq.emplace(distance[adj], adj);
}
}
}
#ifdef DEBUG
for(int i = 0; i < n; i++) {
const auto & adj = pre[i];
cout << i << ": ";
for(int i = 0; i < adj.size(); i++) {
cout << adj[i] << ' ';
}
cout << endl;
}
#endif
if(distance[n-1] == LLONG_MAX) return 0;
vector<int> counts(n, -1);
auto dfs = [&](this auto && dfs, int from) -> int {
if(from == 0) {
counts[from] = 1;
return 1;
} else if(counts[from] > -1) {
return counts[from];
}
counts[from] = 0;
for(const auto & [adj, w]: graph[from]) {
if(distance[adj] + w == distance[from]) {
counts[from] = (counts[from] + dfs(adj)) % MOD;
}
}
return counts[from];
};
return dfs(n-1);
}
};
明天要给学长画图,visio 从未用过,看来得学一学 😵💫
明天不能再摆烂摸鱼了,要刷 leetcode+ 画图 + 复习高数,星期日去市区玩一玩 😋
🫳 🐟️ ❌️
アイラちゃん 🫳 🐟️ ❌️
在アイラちゃん投简历后接近一个月,突然发来了面试通知,到底是什么问题啊 😵💫 我还以为我初筛没过呢,突然跟我说初筛过了明天上午 10 点面试,我怎么抱佛脚啊 😭
JD:
对集群管理、机器学习平台、Kubernetes/Docker 有基本认识,熟悉 Linux 操作系统。乐于助人,耐心细致。计算机科学、软件工程等相关专业。熟练使用 Python/C++,掌握至少一种深度学习训练框架,具备 Megatron/DeepSpeed 框架研发经验者优先。
AI infra 实习
话说アイラちゃん是要保研考研还是就业哎(
此人不语,只是一味地在门里生产废料。
post deleted by author 保研>考研>就业 目前是这样
所以重心其实在前两者,所以刷 leetcode 防机考,目前一半复习数学 +408,剩下时间做 lab/忙实验室组里的东西
不太想卷,只想找个代码相关的工作最后,读硕士也是想攒实习经历 + 学习一些更多的技术
记录:对着 JD 抱 docker、k 哈基 s 的佛脚
不全面的面筋
Docker & k8s
Docker
-
运行于 OS 上的软件,用于创建,管理,编排容器。可以将开发的应用程序自动部署到容器。
-
与 VM 的区别
-
docker 应用层抽象,容器间通过网络命名空间进行隔离。多个容器共享一个 OS 内核。
-
VM 对物理硬件层抽象,包含独立操作系统。
-
前者为应用环境提供,后者为操作系统环境提供。
-
docker 组件
-
引擎(客户端,服务端),docker 镜像,容器,Registry(镜像仓库)
-
架构:Client(Application)-Server(OS)架构。
Docker 引擎
-
Docker 引擎主要有:docker 客户端,docker 守护进程 (daemon),containerd 和 runc。
-
现在的 docker 引擎架构:
-
docker client(CLI) 与 docker daemon(API 与其它特性)进行交互。
-
docker daemon 与容器的监督者 (supervisor) 进行交互,有 start|stop|pause...等管理容器。
-
containerd 负责启动没有守护进程的容器 (shim),每个 shim 运行时有 runc 作为内核源语接口,与运行容器进行交互。
-
Runc: OCI 容器的运行时规范的参考实现。用于创建容器。OCI:运行时标准、容器镜像标准。
-
containerd: 进行容器的生命周期管理——start|stop|pause|rm...。现在也可以用于管理镜像,例如拉取、推送,镜像数据/容器数据的存储。
-
根据镜像启动容器:
docker container run --name ctrl -it alpine:latest sh -
基于 alpine:latest 创建一个名为 ctrl 的容器,并进入 shell 环境。
-
cli 向 docker 守护进程接收指令,指示在 containerd 启动新的容器。
-
containerd 向 runc 传递 OCI 镜像并指示 runc 创建容器并启动 shell 环境。
-
创建容器
-
shim:与 daemon 守护解绑,实现没有 daemon 的容器。当创建新容器后,fork 出来的 runc 进程会退出。随后 containerd-shim 进程将成为容器的父进程。shim 将:
-
保持 stdin 和 stdout 开启。daemon 重启时容器不会因为管道关闭而终止。
-
退出状态反馈给 daemon。
Docker 镜像
-
镜像是一个只读的模板,独立的文件系统,(很像一个停止运行的容器),包括运行容器所需的数据,可以用来创建新的容器。
-
容器从镜像启动后,两者之间就变成了互相依赖的关系。在镜像上启动的容器全部停止之前,镜像是无法删除的。
-
镜像仓库:docker 镜像储存在镜像仓库服务中。一个仓库中可以拥有多个镜像。
-
镜像的命名和标签:采用
:分离。我们可以通过docker image pull <repo>:<tag>来拉取镜像。-a拉取仓库中所有的镜像。 -
一个镜像可以拥有多个标签。
-
filter可以用来过滤。 -
镜像由几个只读的平行层组成。通过
docker inspect可以了解这些层之间的关系。修改或创建新的内容时,将会在这些镜像层之上,创建新的镜像层。通过存储引擎(现在是快照)的方式实现堆栈,对外展示成一个统一的文件系统。 -
可以通过多架构镜像来适配当前运行环境。
--platform指定拉取对应架构镜像,也可以指定运行什么架构的容器。 -
docker image rm <ID>删除对应 ID 的镜像。同时,docker image rm $(docker image ls -q) -f删除所有本地系统内的镜像。
Docker 容器
-
镜像的运行时实例。通过
docker container run <image> <app>运行镜像中的某个应用。-it将当前终端连接到容器的对应终端上,也就是不在幕后运行镜像。 -
容器 vs 虚拟机
-
相同点:都依赖于宿主机,可以是 notebook,可以是物理服务器,也可以是公有云的一个实例。
-
虚拟机:启动后将全部物理资源通过 hypervisor 全部占有,hypervisor 通过将所有的物理资源划分成虚拟资源打包进入 VM 的软件结构中,这样用户可以使用这些虚拟机。虚拟化的是硬件资源。
-
容器:启动后会唤起所选择的操作系统。OS 占有全部的物理资源,在 OS 之上是 docker 引擎,获取 OS 的资源,例如进程树,文件系统和网络栈。接着将资源分割成相互隔离的结构,成为容器。因此虚拟化的是操作系统。

-
运行容器内的应用(例如运行 bash)后,一旦杀死该应用,容器也会退出、终止。因为这是这个容器的主进程。
-
退出容器但是并没有杀死主进程时,容器仍然在运行。此时不能通过 docker run,而是通过
docker exec -it <ID> <app>再次连接到进程。 -
docker container stop终止容器,docker container rm删除容器。数据在容器删除之前将不会被丢弃。
应用容器化
-
编写应用代码
-
dockerfile 创建,包含应用描述,依赖,以及如何运行应用。
-
对 dockerfile 进行
docker image build。 -
docker 将应用程序构建到 docker 镜像中。
对应的一些指令
-
from: 指定基础镜像作为基础镜像层。
-
label: 自定义的标签,是一组 KV 对。
-
run: 执行基础镜像层的应用,可能会安装新的应用,也是一个镜像层。
-
copy: 选取文件复制到当前镜像中,并且新建一个镜像层来存储。
-
EXPOSE: 暴露端口。
-
ENTRYPOINT: 指定入口程序。不新增镜像层。
-
docker image build -t <image>:<tag>. -
增加新的 tag 用于推送:需要
-
仓库服务,仓库,镜像标签。
-
docker image tag <image>:<tag> <repo>/<image>:<tag> -
docker image push推送到上游。
docker compose
-
单引擎进行多容器应用的部署与管理。
-
compose 文件可以使用 yaml 或者 json(前者是后者的子集)来编写。默认为
docker-compose.yml。 -
docker-compose up启动应用。 -
文件包括:version 版本,service 服务,networks 创建新的网络,volumes 创建新的卷。
docker swarm
-
集群管理。一个 swarm 由一个或多个 docker 节点构成。这些节点可以是服务器,虚拟机,树莓派或者云实例。要求通过可靠网络连接。
-
节点将会被配置成管理节点 (Manager) 或者工作节点 (worker)。
-
管理节点:负责控制平面,监控集群状态,分发工作任务,管理工作节点。
-
工作节点接受来自管理节点的任务并执行。
k8s(kubernetes)
-
开源的容器集群管理,提供集群的自动部署,扩缩容,维护等功能。分为管理节点和工作节点,类似于 docker swarm。
-
通过 CI/CD(持续集成/持续交付) 自动化流程,保证环境一致性 (CI),并确保集群可以随时部署 (CD).
组件
-
etcd: 集群状态。
-
apiserver: 提供资源操作的唯一入口,提供认证,授权,访问控制,API 注册,发现等机制。
-
controller manager: 集群状态维护。
-
scheduler: 资源调度,根据预定调度策略将 pod 调度到相应的机器上。
-
kubelet: 负责维护容器的生命周期,同时也负责卷和网络的管理。
-
runtime: 镜像管理,pod 和容器的真正运行。
-
kube-proxy: service 提供 cluster 内部的服务发现和负载均衡。
一些概念
-
Pod:最小的可部署单元。包含一个或者多个紧密耦合的容器。Pod 的主要作用是提供一个环境,可以让容器共享网络和存储资源。并且提供了容器间通信、生命周期管理等功能。
-
Deployment:部署无状态应用程序。可以随时获知当前 Pod 的部署进度。
-
service: 定义了 Pod 的逻辑集合和访问该集合的策略,是真实服务的抽象。提供了一个统一的服务访问入口和代理服务发现机制,关联多个相同 label 的 pod。
-
volume: 卷宗,一个可以被多个容器共同访问的共享目录,定义在 pod 上,可以被一个或多个 pod 中的容器挂载到某个目录下。
-
namespace: Multi-tenant 多租户隔离,将集群内部资源对象分配到不同的 namespace 中,形成逻辑上不同的小项目,小组,用户组,便于共享集群资源的同时还能被分别管理,精细化管理的粒度。
-
1Ingress:通过定义规则来管理从外部访问集群内 Service 的流量。
アイラちゃん 已收藏 (?)
面试超级短(35min),之前抱得佛脚一点没用上,总结如下。(请大家不要随便传播谢谢)
总结
中国电信:AI infra 实习
没有手撕算法部分。(国企这么好的吗)
O、根据简历自我介绍
一、拷打项目:
- 操作系统项目的简单介绍?
- 页表是怎么配置的?(内核启动时配置高页表,随后配置低地址页表,随后启动用户态程序)
- 介绍一下 linux 如何管理内存。(伙伴系统和 slab 分配器)
3.1 两者的区别是什么?(slab 分配器将内存块作为对象进行管理,进行小内存的分配,较大内存的分配通过页表进行。管理对象的生命周期和分配,这样不会在使用完内存后真正释放内存,降低时延和开销。伙伴系统是真正进行内存管理的工具,减少外部内存碎片,通过 2^n 进行分配防止内存碎片。)
3.2 slab 分配器的管理数据结构?(红黑树)。 - IPC 通信方式你选择了什么?(共享内存,这样每个进程访问相同的物理内存,提高进程通信效率)
4.1 每个进程是怎样访问相同的物理内存的?(通过自身的虚拟地址空间,以及自身的页表通过 TLB 的方式来进行地址翻译成为物理内存进行访问)。
小结:操作系统问的不深。没有问到特别高深的技术(写时拷贝,锁,时钟中断 IRQ 等)
二、拷打项目:
- 介绍以下你的阵列是如何进行加速的?
(脉动阵列 (systolic array),专门为矩阵运算进行翻译和加速。矩阵运算取的是两个矩阵的一行、一列进行乘累加操作,因此可以让矩阵的一行自左向右进入乘累加单元,让另一个数据自上而下流入乘累加单元,最后进行操作,中间值存储在寄存器之中。) - 对于大的矩阵乘法应该怎么做?(分块,计算出分块矩阵值后进行存储)
- 对于大型矩阵(分块矩阵)你的项目是否做到了 Cache 的优化?你提到了分块矩阵的存储(很遗憾没有,不过可以参考 flash attention 的方式,进行条块化和重计算后通过 HBM 优化)
建议:项目还要进行优化和深化。
三、深度学习、机器学习系统
- 你了解 Megatron 吗?(是一个大规模 LLM 训练系统,通过结合数据并行,张量并行,流水线并行的 PTP 混合并行方式来进行的系统。)
- 介绍一下 Megatron v1,v2 和 v3 是怎么进行优化的。
megatron v1 主要提出了张量并行的方式来优化 GPU 的内存不足问题。某些模型的权重 + 中间激活值太大无法储存在 GPU 中,因此将他们进行分割,分别储存在不同的节点当中。这样就可以减少不同节点之间的内存不足问题。
缺陷是没有考虑到 TP 所带来的大量通信开销问题。
2.1 向量并行一般有哪些方式?(行并行,列并行),为什么采取后者?(具有非线性激活,无法通过简单的线性防止进行分块矩阵计算,因此需要进行列并行处理)
megatron v2 采用了张量并行,数据并行和流水线并行的方式进行。
2.2 流水线并行进行了什么优化?(1F1B 交叠方式进行,这样显著减少了运算之间流水线的气泡占比)。
2.3 工作节点数目和数据并行,张量并行和流水线并行的关系?(右边三个东西的乘积就是所需的工作节点数)
2.4 microbatch 太大了有什么问题?太小了有什么问题?(microbatch 太大会导致单个节点计算缓慢,增加流水线周期。太小会导致频繁地通信,通信开销增大导致严重的通信开销)
megatron v3 采用激活重计算。因为我没有时间读完因此直接说了面试官也就没有拷打这一部分 QAQ
四、介绍以下目前的慢节点项目(组里)
- 检测到慢节点后如何恢复?(搬出了 Falcon 提出的解决办法,4 层级恢复:1.继续观察性能 2.数据重新划分 3.并行拓扑重新划分 4.检查点重启)
- 怎样才能够进行重新调度?你们有了具体方案吗?(诚实回答目前还在调研中,雏形的想法是通过 kv 的方式简化保存的内容,随后再从检查点中重新恢复。)
- 对于链路的检测和缓解,你认为应该采取什么方案?(对于拥塞,部署在网络层的全局拥塞感知已经在 Sigcomm14 上提出。通过 clos 两层架构,在叶子结点间传递相对应的拥塞指标进行。第一个是分布式架构才可以正确进行链路调度否则会出现问题(举例说明了 ECMP 性能降级),第二个是网络层可以避免绕开内核栈的问题,降低应用层复杂度)
- 目前你们将采用怎样的方式进行?(RDMA 传递,绕开内核直接写入内存)
五、有什么想问的问题?(公司目前的研发目标?有自研 teletron(感觉是 megatron 套皮没有什么新的技术在里面),也在多个层级上有进行研究)
六、评价一下自己?(目前自己的知识还比较碎片化,觉得系统的研究需要有完整的成体系的知识,以及更加深刻的了解,此外对于分布式的知识掌握仍然在入门,需要更加细致地了解相关知识)。
下午要把 leetcode 刷了,一日无 coding 则手生,两日无 coding 则手废矣,等到天晴了再出去玩一天 😋
下周二二面,啊~~~~好累
先更猫猫,回去洗完澡写完最后一题再更

2025 年 6 月 13 日 星期五 天气:小雨
上午抱佛脚➕面试 https://xjtu.app/t/topic/14653/119
中午摸鱼 1 个半小时 🥺
下午学长又要求开会,给我们安排了新的任务,不想再看论文 + 想负载均衡算法了,累
晚上做题非常不顺手,首先每日一题就给我暴击
二分 + 贪心,这种题目就是专门增加调试时间 + 卡贪心考虑不足的 QAQ
C++
class Solution {
public:
int minimizeMax(vector<int>& nums, int p) {
sort(nums.begin(), nums.end());
auto check = [&](int mx) -> bool {
int cnt = 0;
for(int i = 0; i < nums.size() - 1; i++) {
if(nums[i+1]-nums[i] <= mx) {
cnt++;
i++;
}
}
return cnt >= p;
};
int left = 0, right = nums.back() - nums[0];
while (left < right) {
int mid = (left + right) >> 1;
if (check(mid)) {
right = mid;
} else {
left = mid + 1;
}
}
return left;
}
};
然后战一些很奇怪的网格图 dijkstra。
这题可以用并查集,也可以用 dijkstra。并查集的思路就像水逐渐淹过去一样,建立一个高度 -> 索引的映射即可,这里直接用 vector 就好。直到某个时刻起点和终点相互连接。
C++
class Union {
public:
vector<int> parents;
vector<int> size;
int n;
Union(int n):n(n), parents(vector<int>(n)), size(vector<int>(n, 1)) {
iota(parents.begin(), parents.end(), 0);
}
int find(int id) {
if(parents[id] != id) {
return find(parents[id]);
}
return id;
}
void merge(int id1, int id2) {
int pa1, pa2;
pa1 = find(id1), pa2 = find(id2);
if(pa1 == pa2) return;
else if(size[pa1] < size[pa2]) {
parents[pa1] = pa2;
size[pa2] += size[pa1];
} else {
parents[pa2] = pa1;
size[pa1] += size[pa2];
}
}
};
class Solution {
public:
int swimInWater(vector<vector<int>>& grid) {
int m = grid.size(), n = grid[0].size();
vector<int> indices(m*n); // Tips: No repeat.
for(int i = 0; i < m; i++) {
for(int j = 0; j < n; j++) {
indices[grid[i][j]] = i*m+j;
}
}
constexpr int dir[4][2] = {{-1, 0},{1, 0},{0, -1},{0, 1}};
Union u(m*n);
// Merge.
int t;
for(t = 0; t < m*n; t++) {
int cur = indices[t];
int cur_i, cur_j;
cur_i = cur / m, cur_j = cur % m;
for(int i = 0; i < 4; i++) {
int new_i = cur_i + dir[i][0], new_j = cur_j + dir[i][1];
if(new_i >= 0 && new_i < m && new_j >= 0 && new_j < n) {
int adj_height = grid[new_i][new_j];
if(adj_height <= t) {
u.merge(cur, new_i * m + new_j);
}
}
}
if(u.find(0) == u.find(m*n - 1)) {
break;
}
}
return t;
}
};
dijkstra 算法就是网格图,不需要建图(会导致非常稠密浪费时间和空间)。
C++
// The Dijkstra algorithm.
constexpr int directions[4][2] = {{-1, 0},{1, 0},{0, -1},{0, 1}};
class Solution {
public:
int swimInWater(vector<vector<int>>& grid) {
int m = grid.size(), n = grid[0].size();
int N = m * n;
priority_queue<pair<int,int>, vector<pair<int,int>>, greater<pair<int,int>>> pq;
vector<int> distance(N, INT_MAX);
distance[0] = grid[0][0];
pq.emplace(distance[0],0);
while(!pq.empty()) {
const auto [dist, cur] = pq.top();
pq.pop();
if(dist > distance[cur]) continue;
// Coordinate decomposition. cur = i * m + j.
int cur_i = cur / m, cur_j = cur % m;
for(int i = 0; i < 4; i++) {
int adj_i = cur_i + directions[i][0];
int adj_j = cur_j + directions[i][1];
int adj = adj_i * m + adj_j;
if(adj_i >= 0 && adj_i < m && adj_j >= 0 && adj_j < n) {
int new_dist = max(dist, grid[adj_i][adj_j]);
if(distance[adj] > new_dist) {
distance[adj] = new_dist;
pq.emplace(distance[adj], adj);
}
}
}
}
return distance[N - 1];
}
};
目前还想不出来,明天再战(对不起 ldx 想睡觉 😭)
主要的想法就是这题目比较复杂,我们需要了解到,这样一个稠密图我们只需要计算需要进行计算的邻边。我们的目标是 start -> target,因此第一条邻边就是两者之间的曼哈屯距离~
接下来需要考虑的是特殊边。我们看下图就明白应该执行怎样的“松弛操作”(算法导论中的术语)
主要是不能晕掉,牢靠把握 dijkstra 的流程:
寻找起点的邻边 -> 进行松弛操作 -> 更新则加入队列 -> 对新的邻边进行松弛操作 -> 直到终点为止。图中就是分别对 j 进行了邻边 i 的松弛操作,对 n-1 进行了 j 的松弛操作。
然后就是防止内存爆炸 需要对 进行编码操作。这里编码采用 (x << 32)|y ,解码采用 x = key >> 32, y = key & ((1ll << 32) - 1) 的方式进行。

C++
using ll = long long;
constexpr ll MASK = (1ll << 32) - 1;
class Solution {
public:
ll coord2Key(int i, int j) {
ll key = ((ll)i << 32) | j;
return key;
}
pair<int,int> key2Coord(ll key) {
int i = key >> 32;
int j = key & MASK;
return make_pair(i, j);
}
int minimumCost(vector<int>& start, vector<int>& target, vector<vector<int>>& specialRoads) {
priority_queue<pair<ll,ll>, vector<pair<ll,ll>>, greater<pair<ll,ll>>> pq;
unordered_set<ll> visited;
unordered_map<ll, ll> distance;
int start_x = start[0], start_y = start[1];
int target_x = target[0], target_y = target[1];
ll s = coord2Key(start_x, start_y), t = coord2Key(target_x, target_y);
distance[s] = 0;
pq.emplace(0, s);
while(!pq.empty()) {
const auto [dist, cur] = pq.top();
pq.pop();
if(cur == t) break;
if(visited.find(cur) != visited.end()) continue;
visited.insert(cur);
// Update current point to target.
const auto & [cur_x, cur_y] = key2Coord(cur);
if(distance.find(t) == distance.end() || distance[t] > dist + llabs(cur_x - target_x) + llabs(cur_y - target_y)) {
distance[t] = dist + llabs(cur_x - target_x) + llabs(cur_y - target_y);
pq.emplace(distance[t], t);
}
// Update the special roads' ends. start -> cur --- (sp.road) ---> end;
for(const auto & roads: specialRoads) {
ll new_dist = dist + (ll)abs(roads[0] - cur_x) + (ll)abs(roads[1] - cur_y) + roads[4];
ll adj = coord2Key(roads[2], roads[3]);
if(distance.find(adj) == distance.end() || new_dist < distance[adj]) {
distance[adj] = new_dist;
pq.emplace(distance[adj],adj);
}
}
}
return distance[t];
}
};
祝各位晚安~
写累了,稍微总结一下今天写的题目
乍一看没反应过来这怎么用 dijkstra 做,稍微看了看提示,原来是当 成立的时候,将边权设立为 即可。
判断质数采用埃氏筛即可。这一部分预处理完成。
C++
constexpr int MAXN = 1e4+5;
bool notPrime[MAXN]; // When not prime, mark true.
// Eratosthenes prime number calculation.
int init = []() -> int {
notPrime[0] = true, notPrime[1] = true;
for(int i = 2; i < MAXN; i++) {
if(notPrime[i] == false) {
for(int j = i * i; j < MAXN; j += i) {
notPrime[j] = true;
}
}
}
return 0;
}(); // found all prime number.
auto length = [](int num) -> int {
int len = 0;
while(num) {
num /= 10;
len++;
}
return len;
};
class Solution {
public:
int minOperations(int n, int m) {
if(notPrime[n] == false || notPrime[m] == false) {
return -1;
}
int len_n = length(n);
vector<int> distance(pow(10, len_n), INT_MAX);
priority_queue<pair<int,int>, vector<pair<int,int>>, greater<pair<int,int>>> pq;
pq.emplace(n, n);
while(!pq.empty()) {
const auto [dist, cur] = pq.top();
pq.pop();
if(cur == m) return dist;
if(dist > distance[cur]) continue;
int ratio = 1;
for(int digit = cur; digit > 0; digit /= 10) {
if(digit % 10 < 9) {
int adj = cur + ratio;
if(notPrime[adj] && distance[adj] > dist + adj) {
distance[adj] = dist + adj;
pq.emplace(distance[adj],adj);
}
}
if(digit % 10 > 0) {
int adj = cur - ratio;
if(notPrime[adj] && distance[adj] > dist + adj) {
distance[adj] = dist + adj;
pq.emplace(distance[adj],adj);
}
}
ratio *= 10;
}
}
return -1;
}
};
换一些思路做一下 dp
这样我们的转移就是在 上倒序进行的,并且可以优化成两个一维数组。
C++
class Solution {
public:
int minPathCost(vector<vector<int>>& grid, vector<vector<int>>& moveCost) {
int m = grid.size();
int n = grid[0].size();
// vector<vector<int>> dp = vector<vector<int>>(m, vector<int>(n,0));
int * dp_old = new int [n];
int * dp_new = new int [n];
for(int j = 0; j < n; j++) {
dp_old[j]= grid[0][j];
dp_new[j] = dp_old[j];
}
int ans = INT_MAX / 2;
for(int i = 1; i < m; i++) {
for(int j = 0; j < n; j++) {
int temp = INT_MAX / 2;
for(int k = 0; k < n; k++) {
temp = min(temp, dp_old[k]+moveCost[grid[i-1][k]][j]);
}
dp_new[j] = temp + grid[i][j];
if(i == m-1) ans = min(ans, dp_new[j]);
}
int * temp = dp_old;
dp_old = dp_new;
dp_new = temp;
}
return ans;
}
};
这一题采用记忆化的 dfs 方法来进行。因为我们有重复的分支,因此将其记录下来防止指数级别增长即可。
C++
constexpr int directions[4][2] = {{1,0},{-1,0},{0,1},{0,-1}}; // Directions in four ways, RLUD.
class Solution {
vector<vector<int>> memo;
int m;
int n;
public:
int dfs(int x, int y, vector<vector<int>> & mat) {
if(memo[x][y] != -1) return memo[x][y]; // Prune the dfs branch.
memo[x][y] = 1; // Initial value with 1.
for(int i = 0; i < 4; i++) {
int nx = x + directions[i][0], ny = y + directions[i][1];
if(nx >= 0 && nx < m
&& ny >= 0 && ny < n
&& mat[nx][ny] > mat[x][y]) {
memo[x][y] = max(dfs(nx, ny, mat) + 1, memo[x][y]);
}
}
return memo[x][y];
}
int longestIncreasingPath(vector<vector<int>>& matrix) {
m = matrix.size();
n = matrix[0].size();
// Abnormal situation dealing with m,n has 0.
if(m == 0 || n == 0) {
return 0;
}
memo = vector<vector<int>>(m, vector<int>(n,-1));
int ans = INT_MIN / 2;
// DFS started at each coord.
for(int i = 0; i < m; i++) {
for(int j = 0; j < n; j++) {
ans = max(dfs(i,j,matrix),ans);
}
}
return ans;
}
};
思考:dijkstra 与 dp 的区别?
一般来说在图上进行 dp 的时候需要保证无后效型。即后面更新的动作无法影响到已经更新完成的部分。而 dijkstra 算法具有松弛操作,会根据后面点的更新而影响前者(因为后面更新的节点会加入到队列中,从而回过头来可能更新原先遍历过的点)。因此需要观察是否更新无后效型。
今天好热啊下午又闷又热让アイラちゃん做题进展缓慢 😵💫
不学了今晚摸鱼 🐟️
周末要休息一下 😋
double ![]()

アイラちゃん 进行一个赛博吸猫 🫳
アイラちゃん 好乖的小猫🐱
神秘小维 不知道为什么我看这两小只很像两块砖头🧱 🤣
猫猫砖可爱捏

像这个⬆️
小结一下然后学一下数学~
暴力即可。
C++
class Solution {
public:
int maxDiff(int num) {
string s = to_string(num);
string original = s;
int minNum = INT_MAX, maxNum = INT_MIN;
for(int x = 0; x <= 9; x++) {
for(int y = 0; y <= 9; y++) {
for(int i = 0; i < s.length(); i++) {
if(s[i] == '0' + x) {
s[i] = '0' + y;
}
}
if(s[0] != '0' && stoi(s) != 0) {
minNum = min(stoi(s), minNum);
maxNum = max(stoi(s), maxNum);
}
s = original;
}
}
return maxNum - minNum;
}
};
对于 Dijkstra 进行更深一步的考察。对于最短路我们每次都需要进行一次松弛操作,于是对于那些比最短路更长一点的第二短路径也进行松弛操作即可。需要注意的是这里的边是无权边(具有相同权重的边也可以视为无权边),就可以采用普通的 queue 进行松弛操作。
C++
// Using BFS to find the shortest path with the second shortest path.
class Solution {
public:
int secondMinimum(int n, vector<vector<int>>& edges, int time, int change) {
// Step 1: Build graph here.
vector<vector<int>> graph(n+1);
for(const auto edge: edges) {
int u = edge[0], v = edge[1];
graph[u].emplace_back(v);
graph[v].emplace_back(u);
}
// Step 2: function that get the next time.
auto step = [&](int curTime) -> int {
int round = curTime / change; // Whether it is green or not. % 2 == 0: green;
if(round % 2 == 1) {
return (round + 1) * change + time; // next green light + edge weight.
}
return curTime + time;
};
// Step 3: BFS for 0-1 shortest path.
vector<vector<int>> distance(2, vector<int>(n+1, INT_MAX)); // distance[0] -> shortest;
queue<pair<int,int>> q; // 0-1 BFS using queue is OK;
q.emplace(0, 1);
distance[0][1] = 0;
while(!q.empty()) {
const auto [dist, cur] = q.front();
q.pop();
for(const auto & adj: graph[cur]) {
int next_time = step(dist); // Calculate new time.
if(distance[0][adj] > next_time) {
distance[0][adj] = next_time;
q.emplace(next_time, adj);
} else if(distance[0][adj] < next_time && distance[1][adj] > next_time) {
distance[1][adj] = next_time;
q.emplace(next_time, adj);
}
}
}
return distance[1][n];
}
};
看到最大中找最小其实第一反应是二分。于是可以进行 dfs 判断访问其他点,从而获得最大值,二分获得目标之下的最大值。但是此时假设边权最大为 ,如果边权很大将会被卡掉时间复杂度 。我们可以看到边权最大值是 , 但是反向建图+dijkstra 的时间复杂度是 , 所以我们选择 dijkstra 即可。
只需要计算出来所有距离的最大值即可。
C++
using pii = pair<int,int>;
class Solution {
public:
int minMaxWeight(int n, vector<vector<int>>& edges, int threshold) {
if (edges.size() < n - 1) {
return -1;
}
vector<vector<pii>> graph(n);
for(const auto & edge: edges) {
int u = edge[0], v = edge[1], w = edge[2];
graph[v].emplace_back(u, w);
}
priority_queue<pii, vector<pii>, greater<pii>> pq;
vector<int> distance(n, INT_MAX);
pq.emplace(0, 0);
distance[0] = 0;
while(!pq.empty()) {
const auto [dist, cur] = pq.top();
pq.pop();
if(dist > distance[cur]) continue;
for(const auto & [adj, w]: graph[cur]) {
if(distance[adj] > max(dist, w)) {
distance[adj] = max(dist, w);
pq.emplace(distance[adj], adj);
}
}
}
int ans = *max_element(distance.begin(), distance.end());
return ans == INT_MAX ? -1 : ans;
}
};
有点难。具体的做法还是比较容易想的,实际写起来会有一点问题。
- 首先还是套 dijkstra。
- 接着统计一下在限度内能够访问到的真正的节点。
- 最后统计超过阈值的边有多少能够访问。
- 计算{u,v}的访问情况。假设有 dist[u],dist[v], 所以左边的点能够继续访问 maxMove - dist[u], 右边的点能够访问 maxMove-dist[v]。很明显要保证非负数。
- 最后将两者相加,取边分裂节点和他的最小值即可。
C++
using pii = pair<int,int>;
class Solution {
public:
int reachableNodes(vector<vector<int>>& edges, int maxMoves, int n) {
vector<vector<pii>> graph(n);
for(const auto & edge: edges) {
int u = edge[0], v = edge[1], cost = edge[2] + 1; // move will be more than 1.
graph[u].emplace_back(v, cost);
graph[v].emplace_back(u, cost);
}
priority_queue<pii, vector<pii>, greater<pii>> pq;
pq.emplace(0, 0);
vector<int> distance(n, INT_MAX);
distance[0] = 0;
while(!pq.empty()) {
const auto [dist, cur] = pq.top();
pq.pop();
if(distance[cur] < dist) continue;
for(const auto & [adj, w]: graph[cur]) {
if(distance[adj] > dist + w) {
distance[adj] = dist + w;
pq.emplace(dist + w, adj);
}
}
}
int ans = 0;
for(const auto & dist: distance) {
if(dist <= maxMoves) ans++; // Calculate two endpoints.
}
for(const auto & edge: edges) {
int u = edge[0], v = edge[1], w = edge[2];
int u_dist = max(0, maxMoves - distance[u]);
int v_dist = max(0, maxMoves - distance[v]);
ans += min(u_dist + v_dist, w);
}
return ans;
}
};
明天打算开始不定期学一下 golang 了
要兑现自己说过的话 😇
如果有能力也想做一些 lab

好不容易晴朗一次,恢复一下


アイラちゃん p3 是哪里啊
长恨此身非我有 何时忘却营营
紫金港第一皮革<・)))><< 图书馆——(旧)电院群楼的路上的桥上
アイラちゃん (旧)电院
悲😭
回来吧我的电院()
长恨此身非我有 何时忘却营营
2025 年 6 月 16 日 星期一 天气:多云/晴
X: @AlicitruSalt

今天是难得一见的好天气。所以上午就被要求写文档了。
先和学长再次确定了他们需要什么,面对节点故障,链路故障和多任务的情况下 ECMP 在两层 clos 数据中心中会怎么表现从而出现拥塞的情况。一上午就是画图/讨论/思考就结束了。
下午正式解决代码库的问题。我们面临的问题如下:
- 基于离散事件网络仿真工具 ns-3.18 开发。
- 当前代码采用 gcc-5/g++-5 才能运行,高于该版本的将会出错。而我也没有余力进行重构,这是一个痛苦的工作。
- 代码采用经典 makefile 模式,需要构造
compile_commands.json来支持 language server(这里采用 clangd)。 - vscode debug 无法进行。
于是首先:
我们寻找到了 ubuntu 18.04 server 版虚拟机。鉴于我的电脑是 M1 芯片,因此选择 aarch64/arm64 版本安装在 UTM 上。(docker 太慢了并且远程调试目前无法解决,太菜 QAQ)
随后,UTM 的屏幕显示 No display available。无法通过屏幕进行任何操作,这是因为上古的 ubuntu 的 grub 引导程序不会从屏幕输出,需要我们新增加一个串行端口。新建一个串行端口即可。

随后我们只安装 openssh 服务器和必需的服务即可。这样就启动了。然后安装 gcc/g++ 5.5,gdb, make, bear,clangd-8 并且在 /usr/bin 创建软连接指向 gcc/g++/gdb/clangd即可。
随后采用 bear make 的方式来构建我们的项目,bear 将会自动追踪 makefile 过程生成我们需要的 compile_commands.json。将其作为 clangd 的参考文件提供给 language server 即可。
最后解决 debug 问题。我们构建文件后,只需要找到目标运行文件,将其作为 launch.json 的 program 的值即可。但是此时就会爆出错误:XXX_debug.so src file doesn't exist。因此我们需要添加一些动态链接库,也就是给我们的 LD_LIBRARY_PATH. 我们只需要提供给环境系统 lib 和项目构建出来的 lib 即可。我们添加上:
{
"environment": [
{
"name": "PATH",
"value": "${workspaceFolder}/build/lib:${env:PATH}"
}
]
}
这样 debug 也就成功完成了。
晚上自然还是刷 leetcode 的。今天复习 floyd 算法后,明天就可以进入 bellman-ford 算法,prim-kruskal 算法的复习了。待图论大部分结束后,打算再转入 dp 和二分的复习。
前缀最小优化 O(n)。 当然可以。
C++
class Solution {
public:
int maximumDifference(vector<int>& nums) {
vector<int> prefix_min(nums.size(), INT_MAX);
prefix_min[0] = nums[0];
for(int i = 1; i < nums.size(); i++) {
prefix_min[i] = min(prefix_min[i-1], nums[i]);
}
int max_diff = -1;
for(int i = 1; i < nums.size(); i++) {
if(nums[i] == prefix_min[i-1]) continue;
max_diff = max(nums[i]-prefix_min[i-1], max_diff);
}
return max_diff;
}
};
Floyd 算法:最关键的就是遍历每个节点,随后判断将节点 k 塞入 u -> v 的道路中是否会小于当前值。
for k = 0; k < n; k++ {
for i = 0; i < n; i++ {
for j = 0; j < n; j++ {
f[i][j] = min(f[i][j], f[i][k]+f[k][j]);
}
}
}
C++
class Solution {
public:
int findTheCity(int n, vector<vector<int>>& edges, int distanceThreshold) {
vector<vector<int>> f(n, vector<int>(n, INT_MAX));
for(int i = 0; i < n; i++) {
f[i][i] = 0;
}
for(const auto & edge: edges) {
int u = edge[0], v = edge[1], w = edge[2];
f[u][v] = w;
f[v][u] = w;
}
for(int k = 0; k < n; k++) {
for(int u = 0; u < n; u++) {
if(f[u][k] == INT_MAX) continue; // Avoid overflow;
for(int v = 0; v < n; v++) {
if(f[k][v] == INT_MAX) continue;
f[u][v] = min(f[u][v], f[u][k] + f[k][v]);
}
}
}
vector<int> candidate(n, 0);
for(int i = 0; i < n; i++) {
for(int j = 0; j < n; j++) {
if(f[i][j] <= distanceThreshold) {
candidate[i]++;
}
}
}
int min_index = 0, min_city = INT_MAX;
for(int i = 0; i < n; i++) {
if(candidate[i] <= min_city) {
min_index = i;
min_city = candidate[i];
}
// cout << candidate[i] << ' ';
}
return min_index;
}
};
尝试一下静态数组吧。
C++
class Solution {
public:
long long minimumCost(string source, string target, vector<char>& original, vector<char>& changed, vector<int>& cost) {
using ll = long long;
ll f[26][26];
int n = 26;
for(int i = 0; i < n; i++) {
memset(f[i], 0x7f, sizeof(ll) * n);
f[i][i] = 0;
}
for(int i = 0; i < cost.size(); i++) {
ll u = original[i] - 'a', v = changed[i] - 'a', w = cost[i];
f[u][v] = min(w, f[u][v]);
}
for(int k = 0; k < n; k++) {
for(int i = 0; i < n; i++) {
if(f[i][k] == 0x7f7f7f7f7f7f7f7f) continue;
for(int j = 0; j < n; j++) {
if(f[k][j] == 0x7f7f7f7f7f7f7f7f) continue;
f[i][j] = min(f[i][j], f[i][k] + f[k][j]);
}
}
}
ll ans = 0;
if(source.length() != target.length()) return -1;
for(int i = 0; i < source.length(); i++) {
int src = source[i] - 'a', tgt = target[i] - 'a';
ll cost = f[src][tgt];
if(cost == 0x7f7f7f7f7f7f7f7f) return -1;
ans += cost;
}
return ans;
}
};
需要枚举子集。枚举子集的方式就是从 i=0 -> i=(1 << n), 判断某一个元素是否被取到可以有:i & (1 << k) 或者 (i >> k) & 1。
时间复杂度枚举 个子集,并做一次 floyd . 也就是 . Floyd 算法需要 空间。
C++
class Solution {
public:
int numberOfSets(int n, int maxDistance, vector<vector<int>>& roads) {
int **f = new int * [n];
int **graph = new int * [n];
// iterate over the subset.
for(int i = 0; i < n; i++) {
graph[i] = new int [n];
f[i] = new int [n];
memset(graph[i], 0x7f, sizeof(int) * n);
memset(f[i], 0x7f, sizeof(int) * n);
graph[i][i] = 0;
f[i][i] = 0;
}
for(const auto & edge: roads) {
int u = edge[0], v = edge[1], w = edge[2];
graph[u][v] = min(w, graph[u][v]);
graph[v][u] = min(w, graph[v][u]);
}
int ans = 0;
for(int subset = 0; subset < (1 << n); subset++) {
// copy the viable f.
for(int i = 0; i < n; i++) {
if((subset >> i) & 1) {
for(int j = 0; j < n; j++) {
f[i][j] = graph[i][j];
}
}
}
for(int k = 0; k < n; k++) {
if(((subset >> k) & 1) == 0) continue;
for(int u = 0; u < n; u++) {
if(f[u][k] == 0x7f7f7f7f || ((subset >> u) & 1) == 0) continue;
for(int v = 0; v < n; v++) {
if(f[k][v] == 0x7f7f7f7f) continue;
f[u][v] = min(f[u][v], f[u][k] + f[k][v]);
}
}
}
bool flag = true;
for (int i = 0; i < n; i++) {
if (((subset >> i) & 1) == 0) continue;
for (int j = 0; j < i; j++) {
if ((subset >> j) & 1 && f[i][j] > maxDistance) {
flag = false;
break;
}
}
if(!flag) break;
}
if(!flag) continue;
ans += 1;
}
return ans;
}
};
今天就是这样了,又没有看 golang,该罚!!!, 大家晚安好梦~,明天上午 11 点二面,到时候再放一下面筋吧。
今天有猫猫 ![]()

アイラちゃん 猫猫怎么每天都在揣手手
アイラちゃん 不懂就问,这是猫猫固定刷新点吗 ![]()
对的,固定点蹲草就好了




