NOIP2018 SC 笔记&总结

    • Day 1 数学
      • 上午
      • 下午
    • Day 2 贪心与分治
      • 上午
      • 下午
        • 贪心
          • 子集类问题
          • 匹配类问题
          • 树上贪心
    • Day 3 搜索
      • 上午
      • 下午
    • Day 4 台风咕咕咕
    • Day 5 计算几何
      • 上午
      • 下午
    • Day 6 动态规划(一)
      • 上午
      • 下午
        • 抽象概念
        • DAG 看 DP
        • DP 方法
        • 例题(做过也要重新想一遍)
    • Day 7 动态规划(二)
      • 上午
      • 下午
        • 动态规划的优化
          • 01矩阵
          • 派大星·扔鸡蛋问题·后续
        • 课间休息:讲评训练
          • Treasure
          • Lucky7
          • Card
          • 附加题
    • Day 8 数据结构
      • 下午
        • 数组
        • 链表
          • 图的存储
        • std::vector
        • std::bitset
        • std::map
        • 队列
        • 双端队列
        • 优先队列
          • 完全二叉树
          • 插入
          • 删除
          • 堆的初始化
          • 堆的删除
        • 并查集
          • 路径压缩
          • 带权并查集(讲题人:我觉得不能这么叫……)
          • 按秩合并
          • 操作撤销
          • 按大小合并
        • std::set
        • Trie 树
        • Hash
          • 技巧
        • Hash 表
        • 倍增 与 ST表
          • 倍增求LCA……
        • 线段树与分治算法
          • LazyTag 和 标记永久化
          • 标记之间的相互影响
          • ZKW 线段树
        • 树状数组
    • Day 9 图论
      • 上午
      • 下午
        • 基本定义
        • 特殊情况
          • 概念
          • 基本特征
          • 特殊情况
        • 图的遍历
        • 图的存储
        • 拓扑排序
        • 最短路
          • Floyd
          • Dijkstra
          • SPFA
          • 差分约束
        • 最小生成树
          • Kruskal
          • Prim
        • 最近公共祖先 LCA
          • 倍增
          • RMQ
          • 树链剖分
        • 缩强连通分量
    • Day 10 网络流
      • 上午
      • 下午
    • 总结

Day 1 数学

上午

T1 高精度gcd,不想码,下一题。

T2 一开始不会做,先下一题。

T3 错排裸题……错排公式是啥来着?

重新看 T2,发现好像连续的一段是一个等差序列。一波带走。

然后开始码 T1……

最后看 T4,欧拉函数?阶乘,阶乘逆元,线性筛法,线性逆元……本来打算写一个判断模数作为因数的个数的写法,后来还是改成线性逆元了……

啊做完了。

结束后被Clin摁在地上D,细节没考虑清楚估计 T4 凉了。

然而数据太水 T4 过了。哈哈哈AK了……啥 T1 TLE了。

下午

讲的很基础,后期可能会填此处坑。

Day 2 贪心与分治

上午

Orzzzx,估计今天要爆零。

T1 明显的贪心。秒了秒了。

T2 不会做,直接跳,到最后都没做它。

T3 马上分析出来每一列是独立的,且只和同色连续段有关,简单想了一种贪心。

然后马上想了一种数据卡掉。继续想贪心。

想到从中央的地方取更优秀,但后续就不是正解的思路了……

光荣GG,提前离场去占座。

结果考试结束的时候才说改成OJ交题……坑 ×1 × 1

下午

贪心

贪心算法,贪心策略。

取数问题(略)

调整思想。

根据最优解的形态,猜想策略。尝试构造反例。数学归纳法,反证法证明。

子集类问题

例题1:选择区间,使选的数量尽量多。

例题2:选择尽可能少的区间,使区间 [0,m] [ 0 , m ] 上每个点至少被一个区间覆盖。

例题2加强版:将区间 [0,m] [ 0 , m ] 改成环。
解:将环变成两个连起来的区间,然后与处理每个左端点所能到的最远的右端点(?)。最后用倍增。

例题3:树的最大独立集(最大化点数,即不带权;带权不能贪心,树形DP)。

例题4:最小生成树。Kruskal。

扩展:最大权基环森林。选出边权和最大的边集的子集,每一个连通块内最多只有一个环。
同理。
有趣的应用:二分图,保证左边点的度数不超过2. 左边点有点权。选定一个匹配,使得匹配左端点权值最大。
解:若左端点 A A 连向右端点 B,C B , C ,则在 B,C B , C 间连一条边权等于A点权的边,然后求一个最大权基环森林。

匹配类问题

目标一般是一个排列。

例题5:[NOIP2012]国王游戏。(略)

例题6:摆棋子。

例题7:逛商店。无脑网络流(划掉)
我们可以尝试构造特殊数据帮助我们贪心。同时考虑“限制最紧”的元素。

树上贪心

可以从叶子到根贪心。

例题8:哈夫曼编码。

……然后电脑没电了。

Day 3 搜索

上午

T1 双向搜索。

T2 惯例先跳(滑稽)

T3 很基础的搜索思路,稍微剪了一下枝。

回来看 T2,发现每一个连通块相互独立。对于一个连通块,设任意一个点的权值是 x x (未知数),然后搜索的过程中将点记成 kx+b k x + b 的形式(只用记 k,b k , b ),遇到环的时候判断答案合法。另外记录 x x 的取值范围,然后解简单的不等式就行了。

感觉又是一场能A……K……的比赛啊……

结果 T2 写出一个bug始终调不出来,草草交题了事。

最后240,还算凑合……

下午

下午讲题人表达能力略水……但选的题很不错,可惜没时间看……(留坑……)

Day 4 台风咕咕咕

咕咕咕咕。

Day 5 计算几何

上午

T1 随便做 O(n2) O ( n 2 )

T2 不可做,再见。

T3 连凸包都不用写,枚举两个点判断对答案的贡献即可。

结果大爆炸,T3 数组开小,T1细节写挂。不必多说,菜是原罪。

下午

讲解很详细,在课上偶然搞懂了斜率优化(Orz LS & Clin),开心。

Day 6 动态规划(一)

上午

今天的题都不太会做……

Haiku,一开始没有往状压想,反而更像是往数位DP想了……
后来发现 Haiku 真的是个状压。OrzLS。

Candies,也是Clin讲了思路……原来是转化那个大式子就能 O(n3) O ( n 3 ) 做。

Keyboard……Clin梅开二度。往字典树想,发现到树上同深度的任意一种情况的概率是一样的。然后只需要讨论深度就行了。

我今天都在想什么啊……这么菜被吊打活该。严重怀疑明天能不能想出一道题。

下午

抽象概念

DAG 看 DP

DP 方法

自顶向下,自底向上(往回取值,往后赋值)

例题(做过也要重新想一遍)

例题 4.1 括号匹配 (N4000) ( N ≤ 4000 )

f[i][j] f [ i ] [ j ] 表示长度为 i i ,当前栈内剩下 j j 个左括号的答案。

f[i][j]=f[i1][j1]+f[i1][j+1] f [ i ] [ j ] = f [ i − 1 ] [ j − 1 ] + f [ i − 1 ] [ j + 1 ]

O(1) O ( 1 ) 卡特兰数。

例题 4.2 行商问题

给定 N N (1N18) ( 1 ≤ N ≤ 18 ) 个城市,已知任意两个城市之间的距离。任意选一个起点,求恰好遍历所有城市一次的最短路径(最短哈密顿通路)。

发现只关注走过哪些城市,现在在哪个城市。

f[pos][S] f [ p o s ] [ S ] 表示当前在 pos p o s 城市,已经到过的城市集合 S S

f[pos][S]=min{f[pos][S]+dist[pos][pos]} f [ p o s ] [ S ] = m i n { f [ p o s ′ ] [ S ] ′ + d i s t [ p o s ′ ] [ p o s ] }

例题 4.3 扔鸡蛋问题

非常有趣,考虑单独开一个坑聊聊这道题。

Day 7 动态规划(二)

上午

Lucky7,完全不会,跳。

Card,他们怎么都做过啊……继续跳

Treasure,想了一个很容易被卡的贪心,又双叒叕以为是正解,交了。

重新Card,将题目转化为轮流 1 − 1 ,谁先减到 1 − 1 谁赢。然后发现派大星一定第一个 1 − 1 ,也一定最后一个 1 − 1 。枚举派大星到 1 − 1 的时候,另外两个被 1 − 1 了几次,而剩什么牌的情况可以用 3 3 的幂计算, 1 − 1 的顺序可以用组合数算。就有 O(mk) O ( m k ) 的做法了。

i=0mj=1kCn1n1+i+jCji+j3m+kij ∑ i = 0 m ∑ j = 1 k C n − 1 + i + j n − 1 C i + j j 3 m + k − i − j

然后发现 i+j i + j 出现多次。考虑把 i i 的含义改为上式的 i+j i + j 。可以得到

i=0m+kj=max{0,im}min{k,i}Cn1n1+i+jCji+j3m+kij ∑ i = 0 m + k ∑ j = m a x { 0 , i − m } m i n { k , i } C n − 1 + i + j n − 1 C i + j j 3 m + k − i − j

提出中间部分

i=0m+kCn1n1+i3m+kij=max{0,im}min{k,i}Cji ∑ i = 0 m + k C n − 1 + i n − 1 3 m + k − i ∑ j = m a x { 0 , i − m } m i n { k , i } C i j

后面加粗部分设为 g[i] g [ i ] ,则考虑 g[i1] g [ i − 1 ] g[i] g [ i ] 的关系,发现具有类似两倍的关系。然后就可以边枚举 i i 边做了,复杂度 O(m+k) O ( m + k ) ,注意预处理。

然而因为细节写不清楚,最后都没交正解,草草交了 O(mk) O ( m k ) 了事。

然后 30+0+60=90,rank3凑合。

下午

动态规划的优化

01矩阵

N,M100 N , M ≤ 100 ,求面积最大的全1子矩形。

最暴力,枚举子矩形 O(NM)2 O ( N ∗ M ) 2 ,判断合法 O(NM) O ( N ∗ M ) 。判断可以优化为 O(1) O ( 1 ) 。当前复杂度 O(N4) O ( N 4 )

枚举右下角,枚举高度,判断左边界能延伸到多远。 O(N3) O ( N 3 )

God Mode on

上帝视角发现了一些问题,上下也要顶到边界。

所以枚举右边界。

问题变成了一个直方图,求直方图最大子矩形(经典单调栈问题)。

扩展:给定长宽 N,M105 N , M ≤ 10 5 障碍点有 105 10 5 ,同样求最大子矩形。

派大星·扔鸡蛋问题·后续

派大星我们去扔鸡蛋吧!

N,M105 N , M ≤ 10 5

同样 M M 只需要考虑 logN log ⁡ N 个。

f[i][j]=min{max{f[k1][j1],f[ik][j]}}+1 f [ i ] [ j ] = m i n { m a x { f [ k − 1 ] [ j − 1 ] , f [ i − k ] [ j ] } } + 1

随着 k k 的改变,转移式子中两个变量各自都是单调函数。

三分?你可以二分差值。 O(Nlog2N) O ( N log 2 ⁡ N )

N,M106 N , M ≤ 10 6 呢?

考虑记忆化。记忆化不止能记录答案,还能更多

考虑对比 f[i][j] f [ i ] [ j ] f[i1][j] f [ i − 1 ] [ j ] 的函数。

f[i][j]=min{max{f[k1][j1],f[ik][j]}}+1;(1ki) f [ i ] [ j ] = m i n { m a x { f [ k − 1 ] [ j − 1 ] , f [ i − k ] [ j ] } } + 1 ; ( 1 ≤ k ≤ i )
f[i1][j]=min{max{f[k1][j1],f[i1k][j]}}+1;(1ki1) f [ i − 1 ] [ j ] = m i n { m a x { f [ k − 1 ] [ j − 1 ] , f [ i − 1 − k ] [ j ] } } + 1 ; ( 1 ≤ k ≤ i − 1 )

大致画出图像发现相似。

可以 O(1) O ( 1 ) 转移。总复杂度 O(NlogN) O ( N log ⁡ N )

小结:考虑转移式之间的交集,发现冗余来优化。

课间休息:讲评训练

Treasure

每次都会膨胀一行/列。

f[i][j][k][l] f [ i ] [ j ] [ k ] [ l ] 当前在哪个位置,放弃了几个位置,发动了几次魔法。

(留坑……)

Lucky7

有趣的条件:不是7的数字不超过3000个。

不考虑7

考虑完前 i i 个数字,当前总长度为 j j 的总方案数。

枚举第 i i 个数字的个数进行转移。

f[i][j]=cntik=1f[i1][jk]Ckj f [ i ] [ j ] = ∑ k = 1 c n t i f [ i − 1 ] [ j − k ] ∗ C j k

复杂度是均摊的(自证)。

考虑0,把0放在最后做,转移变成 Ckj1 C j − 1 k

考虑7?

(留坑……)

Card

这题我现场秒了,细节写挂惨案。

附加题

2018湘潭邀请赛 D 环上黑白球

f[i][j] f [ i ] [ j ] i个球分成j份的贡献

nb=1min{n,m}i=1f[nb][i1]f[m][i]b2 ∑ b = 1 n ∑ i = 1 m i n { n , m } f [ n − b ] [ i − 1 ] ∗ f [ m ] [ i ] ∗ b 2

f[i][j]=ik=1f[ik][j1]k f [ i ] [ j ] = ∑ k = 1 i f [ i − k ] [ j − 1 ] ∗ k

f[i][j]=f[i1][j]+sum[ik][j1] f [ i ] [ j ] = f [ i − 1 ] [ j ] + s u m [ i − k ] [ j − 1 ]

sum s u m 是前缀和。总复杂度 O(nm) O ( n m )

如果有 5000 5000 组数据。

(留坑……)

Day 8 数据结构

下午

T1,Hash

T2,平衡树(划掉),或者开两个堆。

T3,矩阵?

数组

访问一个元素 O(1) O ( 1 ) 。插入/删除一个元素 O(n) O ( n )

链表

访问一个元素 O(n) O ( n ) 。插入/删除一个元素 O(1) O ( 1 )

删除,注意释放内存。

可进化为双向链表。

图的存储

邻接矩阵(bitset,map优化)。邻接表。

std::vector

动态内存的数组。

#include
std::vector v;
v.push_back(x); // 往数组的最后一位插入元素

如何自己实现一个动态内存的数组?
开一个两倍大小的数组,不够用的时候再开一个两倍大的数组替换原数组。

new int[n];

复杂度正确。

std::bitset

一个优秀的bool数组。

#include
std::bitset bs;

可以将它看作size位的二进制数,直接进行位运算。一次位运算的时间复杂度为 O(sizew) O ( s i z e w ) w w 一般为 32 32 64 64

如何自己实现?(什么时候需要自己实现?)
sizew s i z e w 个int/long long存储即可。

std::map

自带离散化的数组

#include
std::map mp; // typeA为你要“离散化”的类型,typeB为数组元素的类型

例如

std::map<int,bool>mp;
mp[1000000000]=true;

如何自己实现?
平衡树(红黑树)

map自己的遍历方式。

map<int,bool>::iterator it;
for(it=mp.begin();it!=mp.end();it++){
    it->first; // 数组下标
    it->second; // 值
}
//*it ----> pair

队列

支持加入一个元素和取出一个元素。

遵从先进先出(FIFO)原则。

用一个数组实现。

想要动态内存,可以写循环队列。

现成的:

#include
std::queueq;
// q.push();
// q.pop();

支持加入一个元素和取出一个元素。

遵从先进后出(FILO)原则。

用一个数组实现。

现成的:

#include
std::stacks;

双端队列

稍作修改,支持从头和尾之一取出元素。

现成的

#include
// #include
std::dequeq;

优先队列

支持加入一个元素和取出一个元素。

元素之间有某种优先级,每次取出优先级最大的元素。

std::priority_queuepq;
std::priority_queue<int,std::vector<int>,std::greater<int> >pq;
// greater 可以自己写一个operator。
// 或者
struct cmp{bool operator()(type a,type b){return ...;}}
std::priority_queuestd::vector,cmp>pq;

优先队列的常见实现。(也可以线段树,zkw线段树)

所有点权都不小于/不大于其儿子点权的二叉树。

大根堆/小根堆

支持:插入一个元素;删除堆顶元素。

完全二叉树

1 1 号点为根, i i 号点的父亲是 i2 ⌊ i 2 ⌋ .

层数 O(logn) O ( log ⁡ n )

插入

大于父亲就与父亲交换,仍然大于继续交换,直到不大于或到根为止。

有趣的结论:权值随机的情况下,插入一个元素的期望时间为 O(1) O ( 1 ) .

删除

n n 号替换 堆顶,然后类似插入的做法,与儿子较大者交换。

堆的初始化

新建空堆,依次加入 O(nlogn) O ( n log ⁡ n )

把元素random_shuffle一下再加进来,期望时间 O(n) O ( n )

可以先随意把这些元素放进完全二叉树,再考虑从底向上令每个子树满足堆性质。
每次只有子树的根需要调整,用与删除堆顶操作中类似的方法,时间复杂度是它到叶子的距离……是 O(n) O ( n ) 的?
完全二叉树所有节点到叶子的距离之和是 O(n) O ( n ) 级别的,故这个做法的时间复杂度为* 严格 * O(n) O ( n ) 的。

堆的删除

如何删除堆中一个元素。

如果知道删除元素在堆中位置,与n号交换再调整即可。

但是堆不支持高效查找,很可能不知道要删的元素的位置。

(Clin堆了解一下!围棋)

我们只关心堆顶元素,所以可以把删除的元素暂时留在堆内……

实现:再开一个堆存储被删除但留在原堆中的元素,一旦两个堆堆顶相等,就一起弹出。

并查集

快速合并两个集合,查询一个元素属于哪个集合。

建树。

路径压缩

查询一个元素时,将其到根路径上所有元素的父亲都更新为当前的根。

可以看作 O(n) O ( n ) ,但可以被构造数据卡到 O(nlogn) O ( n log ⁡ n )

带权并查集(讲题人:我觉得不能这么叫……)

维护并查集时可以进行一些信息的维护。

集合大小,集合权值和、最值。

也可以支持一个集合中所有权值加 x x 之类的操作。
在根打标记,路径上的标记之和就是这个元素的实际权值。
路径压缩要注意对标记的影响。(?)

按秩合并

秩:并查集维护的有根树的深度。

合并两个集合时,选择深度较小的有根树连接到另一棵上。

O(nlogn) O ( n log ⁡ n )

两个一起做没什么影响, O(n×α(n)) O ( n × α ( n ) ) ,见《算法导论》。

操作撤销

可以支持撤销最后进行的若干次集合合并操作。

未使用任何优化时,直接将合并操作时连接的边删去即可。

使用路径压缩……合并操作连接的边被压缩了难以撤销啊……

只用按秩合并……好多了,不影响。
支持撤销,可以做到 O(nlogn) O ( n log ⁡ n )

按大小合并

也可以按照集合的大小合并。

将较小集合的根连到较大的根上。

显然不超过 O(logn) O ( log ⁡ n ) 条。

vector也适用。同理。(mmp)

std::set

维护有序集合。

#include
std::set st;
// 自定义顺序的方法类似 priority_queue

会自动去重,不需要去重的话可以用 std::multiset s t d :: m u l t i s e t

我以前用过:还有一种用hash实现的set(无序,C++11)。

#include
std::tr1::unordered_seth;

Trie 树

存储多个字符串的数据结构,基本思想是前缀分类。

时间复杂度为字符串总长。空间复杂度为字符串总长乘字符集。

可以用 map 来存储儿子优化空间,但代价是什么?时间多一个 log log

方便统计一个串的所有前缀。

Hash

把一个复杂的信息(大整数,序列,字符串),通过 Hash 算法,提取出对应的特征值(较为简单的信息)。

计算方案数的题目对模数取模。

可以利用Hash快速比较两个信息是否相同(以一个可以接受的概率出现错误)。

技巧

常见Hash方式 f(a)=aiximodp f ( a ) = ∑ a i x i mod p

效率优先:自然溢出,直接用 int,long long 等进行加法、乘法运算,实际上是在模2的次幂下进行的,可以省去取模。

正确性优先:模数尽量采用质数。

双 Hash。

多种方式混合 Hash:乘法加法异或取模……

还可以随机Hash中常用到的常数,例如模数、乘数等,避免被针对。

Hash 表

将复杂信息按其Hash值存储到对应位置上。

可以快速判断一种信息是否出现过,出现过多少次。

需要 Hash 值值域大小的空间,所以 Hash 值值域不能太大。但也会遇到冲突。

拉链法:类似邻接表。足够随机、Hash 值域大于数据量时,每次期望时间 O(1) O ( 1 )

也可以开大值域用 map。

倍增 与 ST表

已知两个 n n 的信息,合并得到 2n 2 n 的信息。就可以在 O(k) O ( k ) 的时间内得到 2k 2 k 的信息。

ST 表:利用倍增思想,对信息进行预处理以支持快速查询。

例题:RMQ(区间最值)。
f(k,i) f ( k , i ) 表示 [i,i+2k1] [ i , i + 2 k − 1 ] 的最大值,则
f(k+1,i)=max{f(k,i),f(k,i+2k)} f ( k + 1 , i ) = m a x { f ( k , i ) , f ( k , i + 2 k ) }
可以找最小的 k k 满足 2k+1rl+1 2 k + 1 ≥ r − l + 1 ,由于重复统计不影响最值, max[l,r]=max{f(k,l),f(k,r2k+1)} m a x [ l , r ] = m a x { f ( k , l ) , f ( k , r − 2 k + 1 ) } .

倍增求LCA……

呵呵哒。除非你动态求 LCA。

线段树与分治算法

都会了吧。

LazyTag 和 标记永久化

……

标记之间的相互影响

……

ZKW 线段树

n n 补到 2 2 的次幂,建成完全二叉树。

可以方便自底向上非递归单点修改。

区间查询也可以实现非递归,区间修改也可以(区间求和——差分;区间最值——标记永久化)。《统计的力量》

可以完全代替堆。

树状数组

ZKW线段树压缩空间版本。

如果递归实现线段树,在知道一个节点及其左儿子的区间和的时候,右儿子的区间和可以相减得到,所以只需要保存左儿子的信息

根据计算机补码的性质, lowbit(x)=x&(x) l o w b i t ( x ) = x & ( − x )

常数优于线段树。

码量优于线段树。

为什么不用树状数组?

思维难度比线段树难太多了。

树状数组强行压缩了空间,较难实现更为复杂的功能。

(没了……我想听平衡树啊喂)

Day 9 图论

上午

T1 是啥?

T2 是啥??

T3 又是啥?

完了一题都不会正解……

LWC:T2 不是费用流吗?

我:先不说noip不会考费用流……今天图论场怎么可能是网络流题啊。

LWC:你确定?

我:这题绝对不是网络流(flag)

最后掉分使我吃柠檬。

然后……

我:我擦你跟我说你们把17日的网络流训练放到今天了?!训练错场是什么操作失误啊!错场后还直接群内全员禁言?这种服务给我的啊?

LWC:……我似乎记得某人说这题绝对不是网络流。

我:……

LWC:2333333333

下午

灵魂画师 ygg

基本定义

将问题抽象为点和边的问题。

特殊情况

有向图,无向图,树(无向无环图),森林,环(有向环,无向环),重边,自环,环套树。

仙人掌:每条边最多在一个环内。
仙人球:每个点最多在一个环内。

DAG:有向无环图。

二分图:

图可以被分成两个部分。
每个部分内部没有连边。
只有两个部分之间有连边。
网络流中经常用到。

概念

根节点,叶子节点;父亲,儿子;祖先,子树;以某个点为根时,树的高度(深度)。

基本特征

任意两点间的最短路径唯一

特殊情况

二叉树(完全/满),链,菊花。

性质从树高,点度分析。

图的遍历

DFS,BFS;DFS 树,BFS 树;DFS 序,BFS 序。

图的存储

邻接矩阵,邻接表。

拓扑排序

略。

最短路

Floyd

多源多汇。

// a[][]为邻接矩阵
for(i=1;i<=n;i++)
    for(j=1;j<=n;j++)
        if(a[i][j])dist[i][j]=a[i][j];
        else dist[i][j]=Inf;
for(k=1;k<=n;k++)
    for(i=1;i<=n;i++)
        for(j=1;j<=n;j++)
            dist[i][j]=min(dist[i][j],dist[i][k]+dist[k][j]);

Time: O(n3) O ( n 3 ) ;Memory: O(n2) O ( n 2 )

k,i,j k , i , j 在循环的位置不可任意替换。存在负权边也是正确。

Dijkstra

单源多汇。

贪心。从起点开始扩展,假设已经知道了到一些点的最短路,可以得到目前情况下,与这些点相邻的最短路。选择其中最短路最小的点走过去。重复上述操作,每次会多得到一个点的最短路,这样就可以一直贪心地走直到走到终点。

堆优化。

负权边不支持

SPFA

如果该点最短路变小,则把它加到队列末尾。边权不全为1,一个点可能会重复入队。

最坏复杂度 O(nm) O ( n m ) ,稀疏图的期望复杂度 O(km) O ( k m ) k k 是一个小常数。

差分约束

abk1 a − b ≤ k 1
bck2 b − c ≤ k 2
ack3 a − c ≤ k 3

max(ac)=min(k1+k2,k3) m a x ( a − c ) = m i n ( k 1 + k 2 , k 3 )

a a b b 连边,权值为 k1 k 1 b b c c 连边,权值为 k2 k 2 a a c c 连边,权值为 k3 k 3 。(显然单向边)

源为 a a ,汇为 c c ,最短路。

应用

是否存在解 = 是否存在负环
判断负环:存在负环 = 最短路无限小;入队次数大于节点数 = 存在负环;
还有更快的 DFS 判断法。《SPFA 的优化与运用》2009年论文( AAS/根/图论/高级/ )。

求两个变量差的最大值 = 求最短路

解是否唯一 = ……
原图一遍最短路 = 能取到的最小值
原图取反(边方向),边权取反,求最长路 = 能取到的最大值。
如果相同解唯一。

例题:略。

最小生成树

Kruskal
Prim

类似Dijkstra。

每次从相邻一层的点中扩展,从所有相邻边中选最小的一条,或者说选择有最小连边的那个点。

最近公共祖先 LCA

倍增

略。唯一可以动态加点。

RMQ

欧拉序:每走过一条边就把到达的点加入欧拉序。

长度恰好为 2n1 2 n − 1 ,一条边进一次出一次,共 n1 n − 1 条边,还有最开始的 1 1

用欧拉序写出每个点的深度。

就会发现:对于两个点在欧拉序上出现的位置(不妨找第一次出现的位置)为 x,y x , y ,则 LCA 为 [x,y] [ x , y ] 区间内的 深 度 最 小 深 度 最 小 的点。

实现 ST表。注意,单次询问为 O(1) ! O ( 1 ) !

树链剖分

涉及链分治。

缩强连通分量

无向连通图割点:删掉该点,图不连通。
无向连通图割边:删掉该边,图不连通。

Day 10 网络流

上午

昨天的训练题……

T1 倍增求第 k k 个祖先。但最后一个点 n=100000 n = 100000 ,询问数为 m=10000000 m = 10000000 ,可能会T。
注意到最后一个点的 k83 k ≤ 83 ,直接建数组保存求过的答案(记忆化思想)。

T2 我们发现直接枚举不经过的点 v v ,然后floyed的复杂度是 O(n4) O ( n 4 ) 是过不了 n=300 n = 300 的。
考虑派大星告诉我们“用上帝视角看看自己在DP中做了什么匪夷所思的事情“。我们发现,对于两个点之间的最短路,可能不经过很多点,而当你枚举到这个点集时,会重复算很多次同一条最短路。
考虑线段树的思想:对于点编号区间 [l,r] [ l , r ] ,设 mid=l+r2 m i d = l + r 2 。只考虑 [l,mid] [ l , m i d ] 里的点被经过,即floyd中 k k 枚举 [l,mid] [ l , m i d ] 。递归进 [mid+1,r] [ m i d + 1 , r ] 继续做,复杂度不会证,但可以看出优化了很多,直接写吧。

T3 写一个Trie树,然后在上面跑Dfs就行了,很不错的题目。

AK了。

下午

讲题人念稿子……太困了,只能自己看了。

总结

Day 2, 6, 7, 8, 9 值回票价。其他的不能说不好,只能说逊色了一些。(Day 1 除外,问一个调和级数的证明竟然说这不在我讲的范围内?)

其他体验极差。

另外,发现自己在以上所有的专题中都有问题存在。接下来会根据笔记内容和笔记缺漏部分进行整理化归。

你可能感兴趣的:(NOIP)