T1 高精度gcd,不想码,下一题。
T2 一开始不会做,先下一题。
T3 错排裸题……错排公式是啥来着?
重新看 T2,发现好像连续的一段是一个等差序列。一波带走。
然后开始码 T1……
最后看 T4,欧拉函数?阶乘,阶乘逆元,线性筛法,线性逆元……本来打算写一个判断模数作为因数的个数的写法,后来还是改成线性逆元了……
啊做完了。
结束后被Clin摁在地上D,细节没考虑清楚估计 T4 凉了。
然而数据太水 T4 过了。哈哈哈AK了……啥 T1 TLE了。
讲的很基础,后期可能会填此处坑。
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:哈夫曼编码。
……然后电脑没电了。
T1 双向搜索。
T2 惯例先跳(滑稽)
T3 很基础的搜索思路,稍微剪了一下枝。
回来看 T2,发现每一个连通块相互独立。对于一个连通块,设任意一个点的权值是 x x (未知数),然后搜索的过程中将点记成 kx+b k x + b 的形式(只用记 k,b k , b ),遇到环的时候判断答案合法。另外记录 x x 的取值范围,然后解简单的不等式就行了。
感觉又是一场能A……K……的比赛啊……
结果 T2 写出一个bug始终调不出来,草草交题了事。
最后240,还算凑合……
下午讲题人表达能力略水……但选的题很不错,可惜没时间看……(留坑……)
咕咕咕咕。
T1 随便做 O(n2) O ( n 2 ) 。
T2 不可做,再见。
T3 连凸包都不用写,枚举两个点判断对答案的贡献即可。
结果大爆炸,T3 数组开小,T1细节写挂。不必多说,菜是原罪。
讲解很详细,在课上偶然搞懂了斜率优化(Orz LS & Clin),开心。
今天的题都不太会做……
Haiku,一开始没有往状压想,反而更像是往数位DP想了……
后来发现 Haiku 真的是个状压。OrzLS。
Candies,也是Clin讲了思路……原来是转化那个大式子就能 O(n3) O ( n 3 ) 做。
Keyboard……Clin梅开二度。往字典树想,发现到树上同深度的任意一种情况的概率是一样的。然后只需要讨论深度就行了。
我今天都在想什么啊……这么菜被吊打活该。严重怀疑明天能不能想出一道题。
略
略
自顶向下,自底向上(往回取值,往后赋值)
例题 4.1 括号匹配 (N≤4000) ( N ≤ 4000 )
设 f[i][j] f [ i ] [ j ] 表示长度为 i i ,当前栈内剩下 j j 个左括号的答案。
f[i][j]=f[i−1][j−1]+f[i−1][j+1] f [ i ] [ j ] = f [ i − 1 ] [ j − 1 ] + f [ i − 1 ] [ j + 1 ]
O(1) O ( 1 ) 卡特兰数。
例题 4.2 行商问题
给定 N N (1≤N≤18) ( 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 扔鸡蛋问题
非常有趣,考虑单独开一个坑聊聊这道题。
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+j i + j 出现多次。考虑把 i i 的含义改为上式的 i+j i + j 。可以得到
提出中间部分
后面加粗部分设为 g[i] g [ i ] ,则考虑 g[i−1] 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凑合。
N,M≤100 N , M ≤ 100 ,求面积最大的全1子矩形。
最暴力,枚举子矩形 O(N∗M)2 O ( N ∗ M ) 2 ,判断合法 O(N∗M) O ( N ∗ M ) 。判断可以优化为 O(1) O ( 1 ) 。当前复杂度 O(N4) O ( N 4 )
枚举右下角,枚举高度,判断左边界能延伸到多远。 O(N3) O ( N 3 )
God Mode on
上帝视角发现了一些问题,上下也要顶到边界。
所以枚举右边界。
问题变成了一个直方图,求直方图最大子矩形(经典单调栈问题)。
扩展:给定长宽 N,M≤105 N , M ≤ 10 5 ,障碍点有 105 10 5 个,同样求最大子矩形。
派大星我们去扔鸡蛋吧!
N,M≤105 N , M ≤ 10 5
同样 M M 只需要考虑 logN log N 个。
f[i][j]=min{max{f[k−1][j−1],f[i−k][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,M≤106 N , M ≤ 10 6 呢?
考虑记忆化。记忆化不止能记录答案,还能更多。
考虑对比 f[i][j] f [ i ] [ j ] 和 f[i−1][j] f [ i − 1 ] [ j ] 的函数。
f[i][j]=min{max{f[k−1][j−1],f[i−k][j]}}+1;(1≤k≤i) f [ i ] [ j ] = m i n { m a x { f [ k − 1 ] [ j − 1 ] , f [ i − k ] [ j ] } } + 1 ; ( 1 ≤ k ≤ i )
f[i−1][j]=min{max{f[k−1][j−1],f[i−1−k][j]}}+1;(1≤k≤i−1) 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 ) 。
小结:考虑转移式之间的交集,发现冗余来优化。
每次都会膨胀一行/列。
f[i][j][k][l] f [ i ] [ j ] [ k ] [ l ] 当前在哪个位置,放弃了几个位置,发动了几次魔法。
(留坑……)
有趣的条件:不是7的数字不超过3000个。
不考虑7
考虑完前 i i 个数字,当前总长度为 j j 的总方案数。
枚举第 i i 个数字的个数进行转移。
f[i][j]=∑cntik=1f[i−1][j−k]∗Ckj f [ i ] [ j ] = ∑ k = 1 c n t i f [ i − 1 ] [ j − k ] ∗ C j k
复杂度是均摊的(自证)。
考虑0,把0放在最后做,转移变成 Ckj−1 C j − 1 k 。
考虑7?
(留坑……)
这题我现场秒了,细节写挂惨案。
2018湘潭邀请赛 D 环上黑白球
f[i][j] f [ i ] [ j ] i个球分成j份的贡献
∑nb=1∑min{n,m}i=1f[n−b][i−1]∗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[i−k][j−1]∗k f [ i ] [ j ] = ∑ k = 1 i f [ i − k ] [ j − 1 ] ∗ k
f[i][j]=f[i−1][j]+sum[i−k][j−1] 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 组数据。
(留坑……)
T1,Hash
T2,平衡树(划掉),或者开两个堆。
T3,矩阵?
访问一个元素 O(1) O ( 1 ) 。插入/删除一个元素 O(n) O ( n ) 。
访问一个元素 O(n) O ( n ) 。插入/删除一个元素 O(1) O ( 1 ) 。
删除,注意释放内存。
可进化为双向链表。
邻接矩阵(bitset,map优化)。邻接表。
动态内存的数组。
#include
std::vector v;
v.push_back(x); // 往数组的最后一位插入元素
如何自己实现一个动态内存的数组?
开一个两倍大小的数组,不够用的时候再开一个两倍大的数组替换原数组。
new int[n];
复杂度正确。
一个优秀的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存储即可。
自带离散化的数组
#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::queue q;
// q.push();
// q.pop();
支持加入一个元素和取出一个元素。
遵从先进后出(FILO)原则。
用一个数组实现。
现成的:
#include
std::stack s;
稍作修改,支持从头和尾之一取出元素。
现成的
#include
// #include
std::deque q;
支持加入一个元素和取出一个元素。
元素之间有某种优先级,每次取出优先级最大的元素。
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)
维护有序集合。
#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_set h;
存储多个字符串的数据结构,基本思想是前缀分类。
时间复杂度为字符串总长。空间复杂度为字符串总长乘字符集。
可以用 map 来存储儿子优化空间,但代价是什么?时间多一个 log log 。
方便统计一个串的所有前缀。
把一个复杂的信息(大整数,序列,字符串),通过 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 值域大于数据量时,每次期望时间 O(1) O ( 1 ) 。
也可以开大值域用 map。
已知两个 n n 的信息,合并得到 2n 2 n 的信息。就可以在 O(k) O ( k ) 的时间内得到 2k 2 k 的信息。
ST 表:利用倍增思想,对信息进行预处理以支持快速查询。
例题:RMQ(区间最值)。
f(k,i) f ( k , i ) 表示 [i,i+2k−1] [ 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+1≥r−l+1 2 k + 1 ≥ r − l + 1 ,由于重复统计不影响最值, max[l,r]=max{f(k,l),f(k,r−2k+1)} m a x [ l , r ] = m a x { f ( k , l ) , f ( k , r − 2 k + 1 ) } .
呵呵哒。除非你动态求 LCA。
都会了吧。
……
……
n n 补到 2 2 的次幂,建成完全二叉树。
可以方便自底向上非递归单点修改。
区间查询也可以实现非递归,区间修改也可以(区间求和——差分;区间最值——标记永久化)。《统计的力量》
可以完全代替堆。
ZKW线段树压缩空间版本。
如果递归实现线段树,在知道一个节点及其左儿子的区间和的时候,右儿子的区间和可以相减得到,所以只需要保存左儿子的信息。
根据计算机补码的性质, lowbit(x)=x&(−x) l o w b i t ( x ) = x & ( − x )
常数优于线段树。
码量优于线段树。
为什么不用树状数组?
思维难度比线段树难太多了。
树状数组强行压缩了空间,较难实现更为复杂的功能。
(没了……我想听平衡树啊喂)
T1 是啥?
T2 是啥??
T3 又是啥?
完了一题都不会正解……
LWC:T2 不是费用流吗?
我:先不说noip不会考费用流……今天图论场怎么可能是网络流题啊。
LWC:你确定?
我:这题绝对不是网络流(flag)
最后掉分使我吃柠檬。
然后……
我:我擦你跟我说你们把17日的网络流训练放到今天了?!训练错场是什么操作失误啊!错场后还直接群内全员禁言?这种服务给我的啊?
LWC:……我似乎记得某人说这题绝对不是网络流。
我:……
LWC:2333333333
灵魂画师 ygg
将问题抽象为点和边的问题。
有向图,无向图,树(无向无环图),森林,环(有向环,无向环),重边,自环,环套树。
仙人掌:每条边最多在一个环内。
仙人球:每个点最多在一个环内。
DAG:有向无环图。
二分图:
图可以被分成两个部分。
每个部分内部没有连边。
只有两个部分之间有连边。
在网络流中经常用到。
根节点,叶子节点;父亲,儿子;祖先,子树;以某个点为根时,树的高度(深度)。
任意两点间的最短路径唯一
二叉树(完全/满),链,菊花。
性质从树高,点度分析。
DFS,BFS;DFS 树,BFS 树;DFS 序,BFS 序。
邻接矩阵,邻接表。
略。
多源多汇。
// 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 在循环的位置不可任意替换。存在负权边也是正确。
单源多汇。
贪心。从起点开始扩展,假设已经知道了到一些点的最短路,可以得到目前情况下,与这些点相邻的最短路。选择其中最短路最小的点走过去。重复上述操作,每次会多得到一个点的最短路,这样就可以一直贪心地走直到走到终点。
堆优化。
负权边不支持。
如果该点最短路变小,则把它加到队列末尾。边权不全为1,一个点可能会重复入队。
最坏复杂度 O(nm) O ( n m ) ,稀疏图的期望复杂度 O(km) O ( k m ) , k k 是一个小常数。
a−b≤k1 a − b ≤ k 1
b−c≤k2 b − c ≤ k 2
a−c≤k3 a − c ≤ k 3
max(a−c)=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/根/图论/高级/ )。求两个变量差的最大值 = 求最短路
解是否唯一 = ……
原图一遍最短路 = 能取到的最小值
原图取反(边方向),边权取反,求最长路 = 能取到的最大值。
如果相同解唯一。
例题:略。
类似Dijkstra。
每次从相邻一层的点中扩展,从所有相邻边中选最小的一条,或者说选择有最小连边的那个点。
略。唯一可以动态加点。
欧拉序:每走过一条边就把到达的点加入欧拉序。
长度恰好为 2n−1 2 n − 1 ,一条边进一次出一次,共 n−1 n − 1 条边,还有最开始的 1 1 。
用欧拉序写出每个点的深度。
就会发现:对于两个点在欧拉序上出现的位置(不妨找第一次出现的位置)为 x,y x , y ,则 LCA 为 [x,y] [ x , y ] 区间内的 深度最小深度最小 深 度 最 小 深 度 最 小 的点。
实现 ST表。注意,单次询问为 !O(1)! ! O ( 1 ) !
涉及链分治。
无向连通图割点:删掉该点,图不连通。
无向连通图割边:删掉该边,图不连通。
昨天的训练题……
T1 倍增求第 k k 个祖先。但最后一个点 n=100000 n = 100000 ,询问数为 m=10000000 m = 10000000 ,可能会T。
注意到最后一个点的 k≤83 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 除外,问一个调和级数的证明竟然说这不在我讲的范围内?)
其他体验极差。
另外,发现自己在以上所有的专题中都有问题存在。接下来会根据笔记内容和笔记缺漏部分进行整理化归。