BaoTx's Blog

BaoTx的博客

同余最短路

这是一个很有意思的算法,用于解决给定 n 个整数,求这 n 个整数能拼凑出多少的其他整数(n 个整数可以重复取)这一类问题,目的用于优化空间复杂度

例题

种硬币,面额 求能凑出 中,有多少价钱可被凑出。

暴力做法

考虑完全背包,但是 ,不可做

正解

我们用 来作为 这样,我们可以用 来表示 的任意一数
于是,我们对 的所有余数建图,即 0,1,2,3,4,5,6,7 共八个点
接下来我们建边

对于3

(0+3)%8=3 0 to 3
(1+3)%8=4 1 to 4
(2+3)%8=5 2 to 5
(3+3)%8=6 3 to 6
(4+3)%8=7 4 to 7
(5+3)%8=8 5 to 8
(6+3)%8=1 6 to 1
(7+3)%8=2 7 to 2

边权为3

对于7

(0+7)%8=7 0 to 7
(1+7)%8=0 1 to 0
(2+7)%8=1 2 to 1
(3+7)%8=2 3 to 2
(4+7)%8=3 4 to 3
(5+7)%8=4 5 to 4
(6+7)%8=5 6 to 5
(7+7)%8=6 7 to 6

边权为7

对于8

(0+8)%8=0
(1+8)%8=1
(2+8)%8=2
(3+8)%8=3
(4+8)%8=4
(5+8)%8=5
(6+8)%8=6
(7+8)%8=7

这种情况都是自环
边权为8

那么,我们得到了一个有向图,然后从0跑最短路,得到 dis[0~mod-1(7)] ,那么在 中,x的贡献与dis[x%mod(8)]相同
所以,每个余数(x)的贡献为 (m-dis[x])/mod+1。把所有余数贡献相加,便得到了答案

洛谷P3403 跳楼机

我们选xyz中的最小值作为mod,然后围绕这三个数进行同余最短路,最后再正常计数就行了,基本上是板子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
#define ll long long
using namespace std;
ll dis[100005];
ll l[3];
queue < ll > q;
int main() {
ll h;
cin >> h;
for (int i = 0; i < 3; i++) {
cin >> l[i];
}
sort(l, l + 3);
ll mod = l[0];
memset(dis, 0x7f, sizeof(dis));
dis[0] = 0;
q.push(0);
while (!q.empty()) {
ll u = q.front();
q.pop();
for (int k = 1; k < 3; k++) {
ll v = (u + l[k]) % mod;
if (dis[u] + l[k] < dis[v]) {
dis[v] = dis[u] + l[k];
q.push(v);
}
}
}
ll ans = 0;
for (int i = 0; i < mod; i++) {
if (dis[i] <= h) {
ans += (h - 1 - dis[i]) / mod + 1;
// if(dis[i]==0) ans--;// 排除地面(0层)
}

}
cout << ans;
return 0;
}

树的直径

树的直径有两种求法,一种是两次dfs,一种是树形dp

两次dfs

从任意一点跑dfs找到最远点,这个点是直径的一段,再从这个点dfs到最远端,也就是直径的另外一端

这个oiwiki上讲的很细,但我更喜欢他的证明

证明

使用反证法。记出发节点为 。设真实的直径是 ,而从 进行的第一次 DFS 到达的距离其最远的节点 不为 。共分三种情况:

  • 上:

y 在 st 上

,与 为树上任意两节点之间最长的简单路径矛盾。

  • 不在 上,且 存在重合路径:

有重合路径

,与 为树上任意两节点之间最长的简单路径矛盾。

  • 不在 上,且 不存在重合路径:

y 不在 st 上

,与 为树上任意两节点之间最长的简单路径矛盾。

综上,三种情况下假设均会产生矛盾,故原定理得证。

注意
上述证明过程建立在所有路径均不为负的前提下。如果树上存在负权边,则上述证明不成立。故若存在负权边,则无法使用两次 DFS 的方式求解直径

树形dp

在一个子树中,其直径即为包含子树根的最长链与次长链拼接而成

我们记录当 为树的根时,每个节点作为子树的根向下,所能延伸的最长路径长度 与次长路径(与最长路径无公共边)长度 ,那么直径就是对于每一个点,该点 能取到的值中的最大值。
数组记子树最长链

实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <iostream>
using namespace std;
const int N=1e5+5;
int head[N],nxt[2*N],vl[2*N],to[2*N],num=0;
void add(int x,int y,int z){
num++;
to[num]=y;
vl[num]=z;
nxt[num]=head[x];
head[x]=num;
}
int ans=0;
int d[N];
void dfs(int u,int f){
for(int i=head[u];i;i=nxt[i]){
int v=to[i];
if(v==f) continue;
dfs(v,u);
ans=max(ans,d[u]+d[v]+vl[i]);
d[u]=max(d[u],d[v]+vl[i]);
}
}
int main(){
int n;
cin>>n;
for(int i=1;i<n;i++){
int x,y;
cin>>x>>y;
add(x,y,1);
add(y,x,1);
}
dfs(1,0);
cout<<ans;
return 0;
}

2025 7 7 学习报告

树链剖分

是指一种对树进行划分的算法,它先通过轻重边剖分(Heavy-Light Decomposition)将树分为多条链,保证每个点属于且只属于其中一条链,然后再通过数据结构(树状数组、SBT、SPLAY、线段树等)来维护每一条链。

定义

size[i] 以结点i为根的子树中结点的个数;
son[i] 结点i的重儿子;
dep[i] 结点i的深度,根的深度为1;
top[i] 结点i所在重链的链首结点;
fa[i] 结点i的父结点;
pos[i] 在DFS找重链的过程中为结点i重新编的号码,每条重链上的结点编号是连续的。

那么,令 的儿子节点中size值最大的节点,那么称 的重儿子重边,除此之外,所有的边称为轻边。全部由重边构成的路径称为重链

树链剖分的过程

  1. 第一次DFS(预处理)
    从根节点出发,递归遍历整棵树,计算每个节点的sizedepfa,并找出每个节点的重儿子son(即子树最大的儿子)。

  2. 第二次DFS(分配编号)
    再次从根节点出发,优先遍历重儿子,将每个节点分配一个新的编号pos,并确定每个节点所在重链的链首top

  3. 建树维护
    利用线段树、树状数组等数据结构,按照pos数组的顺序建立数据结构,实现对树上路径或子树的区间操作。

  4. 树上操作转化为区间操作
    查询或修改树上两点间路径时,将路径拆分为若干条重链上的区间,依次在数据结构上进行操作。

伪代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
void dfs1(int u, int father) {
size[u] = 1;
fa[u] = father;
dep[u] = dep[father] + 1;
int max_size = -1;
for (int v : G[u]) {
if (v == father) continue;
dfs1(v, u);
size[u] += size[v];
if (size[v] > max_size) {
max_size = size[v];
son[u] = v;
}
}
}

void dfs2(int u, int topf) {
pos[u] = ++cnt;
top[u] = topf;
if (!son[u]) return;
dfs2(son[u], topf);
for (int v : G[u]) {
if (v == fa[u] || v == son[u]) continue;
dfs2(v, v);
}
}

线段树在树链剖分中的维护方法

树链剖分后,通常将每条边的权值映射到其子节点(即边 的权值存储在 上),这样便于用线段树维护。

1. 修改某条边的权值

假设要修改边 的权值,设 ,则只需在线段树上修改 位置的值。

2. 修改某条路径所有边的权值

将路径拆分为若干条重链区间,对每段区间(注意每段区间的左端点要+1,因为边权存储在子节点)进行区间修改。

3. 查询原树中某条路径所有边权的最大值

同样将路径拆分为若干条重链区间,分别查询最大值,取最大即可。

4. 查询/修改某个子树中的边

子树内所有边都可以映射为 区间( 的子树内所有节点,除了 本身)。

例题 P2590 [ZJOI2008] 树的统计

纯板子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 5e5 + 5;
int head[N], nxt[2 * N], to[2 * N], num = 0;
void add(int x, int y) {
num++;
to[num] = y;
nxt[num] = head[x];
head[x] = num;
}
int size[N], son[N], dep[N], top[N], fa[N], pos[N], rev[N];
void dfs1(int x, int fat, int d) {
size[x] = 1;
dep[x] = d;
fa[x] = fat;
son[x] = 0;
for (int i = head[x]; i; i = nxt[i]) {
int v = to[i];
if (v == fat) continue;
dfs1(v, x, d + 1);
size[x] += size[v];
if (size[v] > size[son[x]]) {
son[x] = v;
}
}
}
int label = 0;
void dfs2(int x, int ance) {
pos[x] = ++label;
rev[label] = x;
top[x] = ance;
if (son[x]) dfs2(son[x], ance);
for (int i = head[x]; i; i = nxt[i]) {
int v = to[i];
if (v == fa[x] || v == son[x]) continue;
dfs2(v, v);
}
}

int a[N], tree1[4 * N], tree2[4 * N]; // tree1 max tree2 sum
void build(int p, int l, int r) {
if (l == r) {
tree1[p] = tree2[p] = a[rev[l]];
return;
}
int mid = (l + r) / 2;
build(p * 2, l, mid);
build(p * 2 + 1, mid + 1, r);
tree1[p] = max(tree1[p * 2], tree1[p * 2 + 1]);
tree2[p] = tree2[p * 2] + tree2[p * 2 + 1];
}
void change(int p, int l, int r, int x, int y) {
if (l == r) {
tree1[p] = y;
tree2[p] = y;
return;
}
int mid = (l + r) / 2;
if (x <= mid) change(p * 2, l, mid, x, y);
else change(p * 2 + 1, mid + 1, r, x, y);
tree1[p] = max(tree1[p * 2], tree1[p * 2 + 1]);
tree2[p] = tree2[p * 2] + tree2[p * 2 + 1];
}
int askmax(int p, int l, int r, int x, int y) {
if (x <= l && r <= y) {
return tree1[p];
}
int mid = (l + r) / 2;
int ans = -1e9;
if (x <= mid) ans = max(ans, askmax(p * 2, l, mid, x, y));
if (mid + 1 <= y) ans = max(ans, askmax(p * 2 + 1, mid + 1, r, x, y));
return ans;
}
int asksum(int p, int l, int r, int x, int y) {
if (x <= l && r <= y) {
return tree2[p];
}
int mid = (l + r) / 2;
int ans = 0;
if (x <= mid) ans += asksum(p * 2, l, mid, x, y);
if (mid + 1 <= y) ans += asksum(p * 2 + 1, mid + 1, r, x, y);
return ans;
}
int qmax(int u, int v, int n) {
int ans = -1e9;
while (top[u] != top[v]) {
if (dep[top[u]] < dep[top[v]]) swap(u, v);
ans = max(ans, askmax(1, 1, n, pos[top[u]], pos[u]));
u = fa[top[u]];
}
if (dep[u] > dep[v]) swap(u, v);
ans = max(ans, askmax(1, 1, n, pos[u], pos[v]));
return ans;
}
int qsum(int u, int v, int n) {
int ans = 0;
while (top[u] != top[v]) {
if (dep[top[u]] < dep[top[v]]) swap(u, v);
ans += asksum(1, 1, n, pos[top[u]], pos[u]);
u = fa[top[u]];
}
if (dep[u] > dep[v]) swap(u, v);
ans += asksum(1, 1, n, pos[u], pos[v]);
return ans;
}
int main() {
int n;
cin >> n;
for (int i = 1; i <= n - 1; i++) {
int u, v;
cin >> u >> v;
add(u, v);
add(v, u);
}
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
dfs1(1, 0, 1);
dfs2(1, 1);
build(1, 1, n);
int q;
cin >> q;
while (q--) {
string op;
int u, v;
cin >> op >> u >> v;
if (op == "CHANGE") {
change(1, 1, n, pos[u], v);
} else if (op == "QMAX") {
cout << qmax(u, v, n) << endl;
} else {
cout << qsum(u, v, n) << endl;
}
}
return 0;
}

前言

由于身边有很多台电脑,每次写blog都要pull和push,过于麻烦,于是打算尝试用githubAction自动部署

准备

首先,确保你能在本地运行并将静态文件传到github上。这一部分不是本文主要内容,不做详细描述。接下来,本文你按照一个存储库两个分支来讲解。一个分支为main,存储hexo,另一个分支为gh-pages,作为静态文件存储及github pages的源文件。

接着,把你本地的hexo文件上传到main分支下。获取一个ssh,将公钥上传至githubSSH keys,私钥作为Repository secrets上传到Action secrects(Settings-Secrets and variables-Actions secrets and variables-Repository secrets),不妨设name为SSH_PRIVATE_KEY

做好这些准备工作,就可以来配置action了。

Action详解

你需要在.github/workflows下创建你的action文件,文件名任意,后缀为yaml

基本信息

触发条件按设为push到main分支,node版本设置为最新版。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
name: hexo CI

on:
push:
branches: ["main"]
pull_request:
branches: ["main"]

jobs:
build:
runs-on: ubuntu-latest
timeout-minutes: 60

strategy:
matrix:
node-version: [22.x]

修改__config.yaml

主要修改点在于推送部分deploy

需要使用ssh连接推送,分支是gh-pages

以下是一个可能的示例:

1
2
3
4
deploy:
type: git
repo: git@github.com:{YourUsername}/blog.git
branch: gh-pages

Steps

Checkout repository

我也不知道这个干什么用,但是既然github建议有,那就写上吧。

1
2
- name: Checkout repository
uses: actions/checkout@v4

Setup SSH

用于设置SSH密钥,这样才能在生成完毕后推送到gh-pages分支。

1
2
3
4
5
6
7
8
- name: Setup SSH
run: |
mkdir -p ~/.ssh
echo "$SSH_PRIVATE_KEY" > ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa
ssh-keyscan github.com >> ~/.ssh/known_hosts
env:
SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}

这个时候你设置的SSH_PRIVATE_KEY就起作用了。

hexo 启动

就像你在本地运行一样。

1
2
3
4
5
6
7
8
- name: Install Hexo CLI
run: npm install -g hexo-cli
- name: Configure Git
run: |
git config --global user.name "{这里填你的github用户名}"
git config --global user.email "{填邮箱}"
- run: npm install
- run: hexo g -d

或者,你也可以使用任何一个具有更改你的存储库权限的github用户,不过要注意ssh公钥上传

小帮助

如果你在配置时遇到了一些问题,不妨做一下测试。主要问题就出现在ssh私钥的配置上。

1
2
3
4
5
6
7
8
- name: Test SSH key
run: |
# 只查看前两行,防止泄露完整私钥
head -n 2 ~/.ssh/id_rsa
# 可选:查看文件是否存在、权限
ls -l ~/.ssh/id_rsa
- name: Test SSH
run: ssh -T git@github.com

Test SSH出现return1为正常现象,观察终端返回即可。

总结

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
name: hexo CI

on:
push:
branches: ["main"]
pull_request:
branches: ["main"]

jobs:
build:
runs-on: ubuntu-latest
timeout-minutes: 60

strategy:
matrix:
node-version: [22.x]

steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup SSH
run: |
mkdir -p ~/.ssh
echo "$SSH_PRIVATE_KEY" > ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa
ssh-keyscan github.com >> ~/.ssh/known_hosts
env:
SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}

- name: List environment variables
run: |
# 可选:查看文件是否存在、权限
ls -l ~/.ssh/id_rsa
- name: Test SSH
run: ssh -T git@github.com

- name: Install Hexo CLI
run: npm install -g hexo-cli
- name: Configure Git
run: |
git config --global user.name "{这里填你的github用户名}"
git config --global user.email "{填邮箱}"
- run: npm install
- run: hexo g -d

把它上传到main分支,如果顺利的话你可以看到一个成功的action和一个新的分支gh-pages。注意将githubpages的Build and deployment的分支改为gh-pages

结语

现在,在存储库页面点.号,进入github.dev,你就可以随时随地写blog了。

本文就是这样写完的。

关于作者:初三,HA,蒟蒻

出发前带了6包魔芋爽,担心会不会不够吃。

J 组

这个地方怎么这么堵!

解控后发现 D 盘没有noi文件夹,于是查找 C 盘,居然找到了。速速安装 devcpp 并且测试。值得一提的是,今年居然提供了好看的 lemonlime。

到点开题,10 分钟做了 T1,20 分钟做了 T2。lemon 评测样例结果是全过。然后我发现 T2的题面也是一个样例,测了之后发现*我的循环mn写反了!*遂改正,力挽100pts。预计得分

看了看T3和T4。发现了T4的你只需要求出答案对 998,244,353 取模后的结果,认为T4是个DP。先看T3。从字典树想到了线段树,发现没有一个适合的。考虑了一个小时后吃了一包魔芋爽想起了暴力的力量,写了前缀和+模拟左右端点。预计得分。再考虑特使性质A和B。特殊性质B是一个组合数问题,但我忘记了公式,赛场上凭借记忆推出来了。可以再多,最终得分

赛后发现似乎用map优化就能得到满分(?)
再看T4,发现时间不够了吃了另外一包魔芋爽,请出dfs大神,获得了的高分。

预计得分:

题外话:考试结束前1h时放上了挡板,不过挡了和不挡一样。能看到前边的,旁边的有防窥膜看到时黑屏。

S组

2:25发现压缩包密码已经公布,迅速解压导入lemon。

看了一遍题目,发现没有一道会的。还是觉得T1简单。对着样例想到了一个贪心策略,但是被第二个样例卡掉了。无奈之下看了看特殊性质,发现这个贪心策略似乎是特殊性质B的策略。预计得分

看第二题,一下子就想到了暴力+最小生成树的算法,预计得分。用lemon测试的时候发现样例2超时了,改成scanf单独测试输入都用了整整1.6s!估摸一下是能过得(答案正确)。

吃了一包魔芋爽

T3 T4不会打,预计得分

预计得分约

总结

吃了3包魔芋爽
不管如何,初中的oi生涯应该就这样草草结束了。从小学六年级开始学到初一拿到s二等,初二双一等,我对自己的成长既是满意的,又是不满的。下面就要备战中考了,考不上一个好些的高中这辈子oi也算是结束在这了。

希望自己能双一QAQ

这是一个测试文章,用来验证 Hexo + Butterfly 是否正确渲染中文、数学公式、代码块与标签。

一、中文排版测试

随便写两句中文,看看行宽、字体、间距是否符合预期。

如果你看到的段落舒服不别扭,说明主题字体配置正常。

二、数学公式测试(MathJax)

行内公式示例:

行间公式示例:

如果公式渲染正确,说明你的 MathJax 配置已经生效。

三、代码块测试

下面是一段简单的 C++ 代码:

1
2
3
4
5
6
7
#include <bits/stdc++.h>
using namespace std;

int main() {
cout << "Hello Hexo!" << endl;
return 0;
}

如果代码高亮正常、行距美观,表示你的主题代码渲染器工作正常。

四、列表测试

  • 项目 A
  • 项目 B
  • 项目 C

五、结束语

如果你能顺利看到以上内容,说明你的博客环境已经配置得很不错。你可以开始写正式文章了。

Welcome to Hexo! This is your very first post. Check documentation for more info. If you get any problems when using Hexo, you can find the answer in troubleshooting or you can ask me on GitHub.

Quick Start

Create a new post

1
$ hexo new "My New Post"

More info: Writing

Run server

1
$ hexo server

More info: Server

Generate static files

1
$ hexo generate

More info: Generating

Deploy to remote sites

1
$ hexo deploy

More info: Deployment

0%