杭州集训 Day 5,6
杭州集训 Day 5,6
Day 4 晚上
Day 4 晚上一直在赤石所以没有整理笔记。抽象 syk。
找 syk 要了 QQ,要到了
上下午还是正常的讲课,晚上就开始抽象了
首先是 syk 抛出了一个问题
“你们会加法?请证明加法结合律”
“你凭什么说 1 + 2 = 3”
“加法的定义是什么”
三个问题问蒙全场。然后开始现场教学证明加法交换律。
定义 0, 0 的后继为 1,用 S(0) 表示,那么 2 就用 S(S(0)) 表示
有两个显然的事实:
0 + x = x
S(y) + x = S(y + x)
引理 1:x + 0 = x
当 x 为 0 时,0 + 0 = 0
若该结论对于 x 成立,S(x) + 0 = S(x + 0) = S(x)
引理 2:x + S(y) = S(x + y)
S(x) + S(y) = S(x + S(y))
S(x + y) = S(S(x) + y)
对于 x 成立时,对于 S(x) 成立
加法交换律得证
定理:x + y = y + x
当 x = 0 时,由引理 1 和定义显然
otherwise:S(x) + y = S(x + y) S(y + x) = y + S(x)
该证明的正确性,其实是可以保证的,因为我们使用的定义只有:
x = x 以及 0 是空的
关于乘法 0 * y = 0, S(x) * y = x * y + y 可以这样定义,接下来接着数学归纳就能证明其他的。
(参考文献:《数学基础》----- 汪芳庭)
然后 syk 给我们讲北大挂科了或者全员分太高要怎么处理。
有两个经典操作,导师会把成绩根号诚实掉(设你考了 x 分,那么你最终得分为 根号x * 10)
另一个就是先平方再除以 100
THUPC 2023 年压轴题出了一个叫烂柯杯的题,总的来说就是给你讲一个几千字的故事你猜主人公是谁。戏剧性的一幕出现了,syk 打开了题解,第一篇是未来河北省队队长 jijidawang 的(ReTF 要是写了我就说是 ReTF 了)
小凯的疑惑,一个非常经典的题,简单来说就是推个数学式子答案是一个线性通项公式,然后 syk 说他同学研究的 AI 的,就把这个题喂给了 他们自己研发的 AI, 发现死活不会做,但是这个题难度并不高,后来 debug 了很久以为是 AI 挂了,最后才发现是因为他们喂题的时候丢了一个互质的条件导致没法求解。
完了,一发不可收拾了,syk 开始瞎扯后大家都在瞎扯。
跟 syk 说了再见,说上北京有空找他玩。轮到 ztb 进场了。
ztb 比之前帅了。
笔记
P4180
求最小生成树,然后枚举每条非树边 \((𝑥, 𝑦, 𝑤)\),找到树链 \((𝑥, 𝑦)\)
上的最大值 \(𝑀_1\) 和严格次大值 \(𝑀_2\) ,然后用 \(𝑀_1 == 𝑤? 𝑤 −
𝑀_2: 𝑤 − 𝑀_1\) 更新答案。具体实现树剖 + ST 即可。
CF196E
假设如果现在已经到达了一个传送门,那么剩下的代价就是:所有传送门
点两两相连,边权等于其最短路的完全图的最小生成树。总答
案即为这个最小生成树加上 \(1\) 号点到某个传送门的最短路。
考虑 \(Kruskal\) ,最小边 \((𝑢, 𝑣)\) 一定可以表示成这样的形式,存在某条边 \((𝑥, 𝑦, 𝑤)\) ,使得 $𝑑𝑖𝑠 (𝑢, 𝑣) = 𝑑𝑖𝑠 (𝑢, 𝑥) + 𝑤 + 𝑑𝑖𝑠 (𝑦, 𝑣) $,其中 \(𝑢, 𝑣\) 分别是距离 \(𝑥, 𝑦\) 最近的传送门。
所以可以枚举原图中的 \(𝑚\) 条边 \((𝑥, 𝑦, 𝑤)\) ,然后把所有的
\((𝑢, 𝑣, 𝑑𝑖𝑠 (𝑢, 𝑥) + 𝑤 + 𝑑𝑖𝑠 (𝑦, 𝑣))\) 拿去跑最小生成树
- 最短路跑一遍即可,只需要把所有起点扔进队里即可
- 最后的答案是新的跑出来的最小生成树和 \(dist_1\) 的和
P4208
\(Kruskal\),每次考虑相同权值的所有边,然后用矩阵树定理求出情
况数。不过题中保证相同权值的边 \(≤ 10\) 条,所以可以枚举边集
判断是否是树。
P8207
对于每个数 \(𝑥\) ,如果把 \(𝑥\) 与 \(𝑦\) 连边,代价就是 \(xy / gcd(x,y)\)。那么在 \(gcd( 𝑥, 𝑦)\) 相同时,我们希望 \(y\) 尽可能小。
• 所以枚举 𝑥 ,再枚举这个 gcd = 𝑑(𝑥 的所有约数) ,然后找出
来在 𝐿, 𝑅 中最小的 𝑑 的倍数,然后往 Kruskal 中加入这条边
P5540
将 \(∑𝑎_𝑖\) 和 \(∑𝑏_𝑖\) 分别视作 \(𝑥, 𝑦\) ,那么在二维平面上,最优解一定在凸壳上。然后最大化三角形面积向量乘法然后化简式子后跑 \(kruskal\) 就行。复杂度不会分析,能过。
P5631
因为 \(\text{mex}\) 是不在生成树内的最小的自然数,那我们可以考虑枚举答案是\(x\),\(\text{check}\)的时候,如果一条边的边权是 \(x\),那么这条边就不应该出现在生成树里,否则一定非法。
枚举答案 \(x\),把边权为 \(x\) 的所有边删掉,看剩下的边能不能求出一棵生成树。
分治优化,用 \(solve(l,r)\) 表示权值范围在 \([l,r]\) 的边没有加进去,每次添加 \([l,mid]\) 到 \([mid+1, r]\),添加 \([mid+1, r]\) 到 \([l, mid]\),可撤销并查集维护,复杂度 $ O(m\log m \log w)$
P5406
统计出每个权值的生成树个数 \(\mod 𝑝\) ,第一个 \(≠ 0\) 的就是答案。
考虑 \(w_e=x^{val}\) 就能求出每个权值的生成树个数。
实现上,在高斯消元时,可以让 \(f[i][j][k]\) 表示矩阵中 \(𝑖,𝑗\) 位置 \(𝑥^𝑘\) 的系数。
由于 FWT 是线性变换,所以可以先统一 FWT,然后做高斯消元,最
后再 IFWT 。对 \(⊕\) 运算进行 FWT 的方法是每一位单独考虑,然后对
应位用对应运算的 FWT 方式。
P2149
把既可能出现在 Elaxia 最短路上,又可能出现在 W** 最短路上的
有向边求出来,一定没有环,所以是 DAG。在 DAG 上求最长链。
P4366
优化建边。每个点只向二进制位变动 \(1\) 的点连边。例如 \(10110\) 向
\(00110,11110,10010,10100,10111\) 连边。这样原本一条边被拆成
了 \(𝑖\) \(xor\) \(𝑗\) 条边。
边数为 $𝑛 \log 𝑛 + 𝑚 $,复杂度为 \(𝑂( (𝑛 \log 𝑛 + 𝑚 )\log 𝑛)\)
P7669
考虑哪些边 \((𝑥, 𝑦)\) 可能在 \(𝑠 → 𝑡\) 的最短路上:当且仅当
\(𝑑𝑖𝑠 (𝑠, 𝑥) + 𝑤_{𝑥,𝑦} + 𝑑𝑖𝑠( 𝑦,𝑡) = 𝑑𝑖𝑠 (𝑥,𝑡)\) 。将其称为“可能边”。
一定是先从 \(𝑢\) 走到某个点 \(𝑎\) ,然后沿着“可能边”走一条链到
\(𝑏\) ,然后再走到 \(𝑣\)。
所以我们在“可能边”构成的有向图上 \(dp\)(注意这个有向图是
DAG):对于每个点 \(𝑎\) ,求出其能到达的所有点 \(𝑏\) 中最小的
\(𝑑𝑖𝑠 (𝑏, 𝑣)\) ,然后用 \(𝑑𝑖𝑠 (𝑢, 𝑎) + 𝑑𝑖𝑠 (𝑏, 𝑣)\) 更新答案
P4768
相当于每次询问在经过 \(ℎ > 𝑝\) 的边时,能够找到的最短的 $𝑑𝑖𝑠 (1, 𝑖) $。
假如可以离线,将所有边和询问按照海拔排序,用并查集维护每个连通块
最短的 \(𝑑𝑖𝑠 (1, 𝑖)\) ,加边时合并两个连通块。
强制在线时,考虑把刚才离线的过程存下来。每加一条边就新建一个抽象
节点(表示新的大连通块),然后连向之前两个小连通块的抽象节点。询
问时,从 \(𝑣\) 开始往上倍增,直到 \(ℎ ≤ 𝑝\) 为止。
P3350
分治,每次考虑经过当前分界线的所有路径。枚举分界线上的所
有点,从这个点开始跑最短路。然后用两侧的点的最短路之和更
新每个询问的答案。
复杂度不会分析,因为我没有过,代码留在这,有大佬能给卡进去吗QwQ
#include <bits/stdc++.h>
#define rint register int
#define endl '\n'
using namespace std;
const int N = 2e4 + 5;
const int M = 1e5 + 5;
const int inf = 1e9;
int n, m, q;
int f[N], a[M], b[M], c[M], d[M], ans[M];
bool v[N];
int id(int x, int y) {return (x - 1) * m + y;}
int h[N], e[M], ne[M], w[M], idx;
void _add(int a, int b, int c)
{
e[++idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx;
}
void add(int a, int b, int c)
{
_add(a, b, c); _add(b, a, c);
}
void solve(int l, int r, vector<int> &vec)
{
priority_queue<pair<int, int> > q;
if (l > r) return ;
int mid = (l + r) >> 1;
vector<int> ls, rs;
for (auto i : vec)
{
if (c[i] < mid) ls.push_back(i);
else if (a[i] > mid) rs.push_back(i);
}
for (rint s = 1; s <= m; s++)
{
for (rint i = id(l, 1); i <= id(r, m); i++) f[i] = inf, v[i] = 0;
f[id(mid, s)] = 0;
q.push(make_pair(0, id(mid, s)));
while (!q.empty())
{
int x = q.top().second;
q.pop();
if (v[x]) continue;
v[x] = 1;
for (rint i = h[x]; i; i = ne[i])
{
int y = e[i];
int z = w[i];
if (f[y] > f[x] + z && id(l, 1) <= y && y <= id(r, m))
{
f[y] = f[x] + z;
q.push(make_pair(-f[y], y));
}
}
}
for (auto i : vec)
{
ans[i] = min(ans[i], f[id(a[i], b[i])] + f[id(c[i], d[i])]);
}
}
solve(l, mid - 1, ls);
solve(mid + 1, r, rs);
}
vector<int> vc;
signed main()
{
cin >> n >> m;
memset(ans, 0x3f, sizeof ans);
bool flag = 0;
if (n < m)
{
int x;
swap(n, m), flag = 1;
for (rint j = 1; j <= m; j++)
for (rint i = 1; i < n; i++)
cin >> x, add(id(i, j), id(i + 1, j), x);
for (rint j = 1; j < m; j++)
for (rint i = 1; i <= n; i++)
cin >> x, add(id(i, j), id(i, j + 1), x);
}
else
{
int x;
for (rint i = 1; i <= n; i++)
for (rint j = 1; j < m; j++)
cin >> x, add(id(i, j), id(i, j + 1), x);
for (rint i = 1; i < n; i++)
for (rint j = 1; j <= m; j++)
cin >> x, add(id(i, j), id(i + 1, j), x);
}
cin >> q;
for (rint i = 1; i <= q; i++)
{
cin >> a[i] >> b[i] >> c[i] >> d[i];
vc.push_back(i);
if (flag) swap(a[i], b[i]), swap(c[i], d[i]);
if (a[i] > c[i]) swap(a[i], c[i]), swap(b[i], d[i]);
}
solve(1, n, vc);
for (rint i = 1; i <= q; i++) cout << ans[i] << endl;
return 0;
}
CF757F
求出最短路的 DAG。问题转化为删除一个点,使得删完后 \(𝑠\) 不能
到达的点尽量多。可以不用支配树做,因为此题只是求最大值。
如果某个点入度 \(= 1\) 且入点 \(≠ 𝑠\) ,那么删这个点不如删其入点。
于是可以把整条单链缩成一个点。拓扑排序时,检查当前点的入
点是否缩成了一个点,如果是的话将当前点缩进去。
缩完以后,每个点的入度 \(≥ 2\) (或者入度 \(= 1\) 且入点为 \(s\))。那
么删掉一个点不会导致其它点不可达。所以答案为所有点的 \(𝑠𝑖𝑧𝑒\)
的最大值