学习笔记

11/4

——数集 题解

$2^k$ 个集合,每次标记一个集合的所有子集,可以在 $O(k2^k)$ 内完成。

——染色 题解

两个序列,$a$ 单调递增,$b$ 单调递减,求 $\min(\max(ai,bi))$,用二分法。

——电路板 题解

priority_queue 的仿函数不要使用全局变量,否则可能出现莫名其妙的错误。把比较内容全部写在结构体中。

——史上第二简洁的题面 题解

当要在 $n$ 个 vector 中询问多次 lower_bound 时,可以考虑对于前几个 vector 预处理前缀和优化。

 

11/5

——均分财产 

对于一个有 $n$ 个数的序列 $a$ 满足 $|ai| \leq W$,可以在每个元素前加上±号,使得和的绝对值 $\leq W$。

——史上第三简洁的题面 题解

对于一个只有 bool 值的 dp 方程,可以考虑用 bitset 优化。

 

11/6

——史上第四简洁的题面 题解

 用 Dijikstra 求最短路树时,如果随后要判断一条边是不是树边,一定要记得保存边的编号而不是 `fa[u]!=v && fa[v]!=u`,否则会无法处理重边的情况

同理,对于任何一道题,如果题目中没有明确写没有重边,则判断一条边时,一定要用编号而不是两个端点判断

——白鸽

最小费用最大流可以用 Dinic 加速但是会很长。并且在求解网络流时尽量合并重边,省很多时间不知道为什么

——树上差分

1. 当计算满足 $f(x)=i$ 的 $f(x)$ 个数时,可以先求出满足 $i|f(x)$ 的 $f(x)$ 个数,然后用 Dirichlet 差分(本质上就是一个容斥)。

2. 如果一个算法时间复杂度为 $O(n*\sum{i})$ 之类的,可以考虑阈值划分,即对于 $i \leq B$ 采取以上算法,对于 $i>B$ 采取另一种算法。

——鸽子固定器

如果题目要求一个区间内前 $m$ 大或第 $m$ 大的数的一个函数,可以考虑维护一个链表,每次找到那个第 $m$ 大的数,并向左向右“扩张” $m$ 个数即可。

——新年的小黄鸭

树链剖分可以作为树形 dp 的一个有效方式,方法为递归重链&轻子树(即从重链上长出来的子树)。

——Podatki drogowe

想用二分法,当值域过大时,可以改为随机找出一个,通过比它大的有几个来判断二分递归。

——Invisible

求一个区间内是否存在一个数出现奇数次,可以对每个数赋一个很大的随机权值,然后求区间异或和,若异或和不为0则一定有出现奇数次的数,反之则可以认为没有。

——Numb

求一个集合,可以想办法找到另一个集合,使之与原集合成一一对应,证明不重不漏即可。

 

11/7

——艺术家 题解

1. 对于集合的树上两两合并,不用惊慌,用启发式合并,并且记得是 swap(mp[x],mp[y]) 而不是 swap(x,y),神奇的是,map 的 swap 操作是线性的

2. 当动态/静态统计一个区间内所有数各不相同时,可以考虑开一个 map,然后 mp.size()==len 时即所有数各不相同。注意当 (--mp[x])==0 时要 mp.erase(x)。

3. 动态/静态统计一个区间内所有数各不相同,也可以通过记录 $lst_i$ 为每个位置 $i$ 上一个与该位置上的数相同的位置,则一个区间 $[l,r]$ 内所有数各不相同等价于 $\max_{l \leq i \leq r} lst_i < l$。

 $lst_i$ 可以用一棵线段树动态维护。(但是本题似乎会因常数大而T掉)

——黑白树 题解

1. $k$ 较小时最好能够预处理 $2^k$,省掉那个 $\log mod$(

2. 性质:对于树上任意一条直径 x—y 与任意一个点 u,令 $d=\max(dis(u,x),dis(u,y))$,则对于树上任意另外一个点 v,有 $dis(u,v) \leq d$。

 证明:考虑 u 到直径的距离即可。

——删边方案

1. 二项式反演: $ q_T = \sum_{T \subset S}{p_S} \Leftrightarrow p_S = \sum_{S \subset T}{(-1)^{|T|}q_T} $

 证明很简单,直接展开即可。

2. 按从小到大顺序枚举所有非空子集: 

for(int s00=s&(s-1);s00;s00=(s00-1)&s) { int s0=s^s00; /*...*/ }

  

11/13

——讲课内容

ARC101E Ribbons on Tree:

树上的容斥原理,以边为单位 dp。

WF16 A Balanced Diet:

一个序列要求每个元素有上下界限制时,可以考虑先贪心满足下界,有可能此时上界也已满足。

我忘了来源的题:

一些正在扩张的矩形,它们的面积并是时间的二次函数,可以用插值求出。

HDU6978 New Equipments II:

对于二分图费用流问题,如果只是一些边不存在,可以改用将已访问的点剔除。

——毛毛虫

1. 树上要求一个结点除了子树与祖先之外的结点的xx和,可以通过两次 dfs 实现,第一次正序扫描所有出边,第二次倒序扫描所有出边,维护一个全局变量,每到达一个节点时将全局变量赋给这个结点,将要离开一个结点时更新全局变量。

2. 求异或线性基,开一个数组 a[64],记录最高位为 $i$ 的数 $a_i$,合并、插入简单到你想把自己原来的代码吃了!!1

 


11/14

——数字

1. 和数论有关的题有时候可以相信玄学时间复杂度,有点假的算法也能跑95(

2. 数论分块是个好东西,$g \leq 100$ 时用A算法,$g \geq 10^7$ 时用B算法,其余时候用C算法。(注意调好块长,一开始 $g \leq 10^4$ 时用A算法,$g > 10^4$ 时用B算法结果就95了)

——旅行

1. 再说一遍,n方真的有可能过百万,就算只拿60分,为什么不写出来呢(

2. 树状数组的 $O(1)$ 清空法:

class FTree{
    int n,w,c[N*2];
    int his[N],hcnt;
    
    int lowbit(int x) const
    {
        return x&(-x);
    }
    
public:
    
    void init(int _n)
    {
        n=_n;
        hcnt=1;
    }
    
    void poke(int p,int x)
    {
        while(p<=n){
            if(his[p]!=hcnt){
                his[p]=hcnt;
                c[p]=0;
            }
            c[p]+=x;
            p+=lowbit(p);
        }
    }
    
    int peek(int p) const
    {
        int res=0;
        while(p){
            if(his[p]==hcnt) res+=c[p];
            p-=lowbit(p);
        }
        return res;
    }
    
    void clear()
    {
        hcnt++;
    }
};
class FTree

3. 不要无脑乱用 #define int long long,可以先写上,整个程序写完之后再仔细看看哪些用 int 就可以,会快很多。特别是在各种老爷机上

4. vector 能不用就不用,一维的就别用了,也会快很多。

——字符串

设置 dp 状态时,一定要分清一个状态代表什么,特别是一个状态 f[k] 当 k=n 时代表 k>=n 时的所有情况之类的。此时最好用顺推转移,避免漏掉情况。

——tournament

(不要想太复杂)

 

11/21

——你还没有导光吗 题解

从若干个序列中拿数,要求任何时候和 $\geq 0$,可以顺着做一遍(每拿一段和都要 $\geq 0$ 并计算需要的初值),再逆着做一遍(先求出总和,再把数列中的数取反,从后往前做)。

为什么成立?顺着做完剩下的数和 $<0$,取反后就 $>0$,逆着一定可以弄完。

注意无解判定:①总和 $<0$; ②无论是顺着拿还是逆着拿,某一时刻拿不动了。

——猜数游戏

1. 对 $[1,n]$ 二分猜数,需要次数为 $└ \log_2{n+1}┘$ 而非 $└ \log_2{n}┘$ 。

 

11/22

——猜数游戏

一个很厉害的技巧:翻转 dp 的值域和其中一维。

当发现 dp 状态为 $f(i,j,k)$ 状态数过多时,若发现 $f(i,j,k)$ 的取值范围较小,可以令 $g(i,j,x)$ 表示使得 $f(i,j,k) \leq x$ 成立的最小的 $k$,称为值域翻转。

值域翻转后的转移可以同时处理原转移的单调性。处理时,一定一定要加上无数注释,告诉自己每个东西到底是在递增还是递减。

最好写一小段代码验证一下

 


11/26

——简单无向图

1. 对于 “给定 $l_1,r_1,l_2,r_2$,对于 $l_1 \leq x \leq r1, l_2 \leq y \leq r_2$ 从 $x$ 到 $y$ 连一条边” 的问题,普遍做法都是改为矩形加 1,然后用扫描线之类的方法。

2. 在一个边很多且以隐晦形式给出的问题中,补图转化往往有奇效。

——金色飞贼

动态维护一些线段,每次找出与给定线段相交的所有线段并删除它们:

set< pair<int,int> >::iterator it=st.lower_bound(make_pair(y1,-MAX));

if(it!=st.begin()){
    it--;
    if((*it).second<y1) it++;
}

while( it!=st.end() && (*it).first<=y2 ){
    y1=min(y1,(*it).first);
    y2=max(y2,(*it).second);
    st.erase(it++);
}

st.insert(make_pair(y1,y2));
View Code

 

11/28

——组合

枚举东西还是先固定一个点再枚举好,${n \choose 3}$ 这种写法重复的很难处理

——连通

重要技巧:拉格朗日插值

特别适用于背包类问题,尤其是树上背包。

若出现 $f_{u,i}=\sum\limits_{v_1,v_2, \dots,v_{s} \in son(u)} \sum\limits_{j_1+j_2+ \dots +j_{s}=i} {f_{v_1,j_1}f_{v_2,j_2}\dots f_{v_s,j_s}}$ 这样的卷积形式,

令 $F(u,x)=\sum\limits_{i=1}^{n} f_{u,i} x^i$,则可得 $F(u,x)=\prod\limits_{v \in son(u)} F(v,x)$。

此时 $F(u,x)$ 的次数至多为 $n$,因此记录 $F(u,x)$ 在 $x=0,1,\dots,n$ 处的值,可以 $O(n^2)$ (而非 $O(n^3)$)转移。

“解码”时,如果要多项式的系数,预处理出式子 $\prod\limits_{j \neq i}{\frac{x-j}{i-j}}$ 中 $x^k$ 的系数 $d_k$。

然后对于一个 $u$,$[x^k]F(u,x)=\sum\limits_{x=0}^{n}{d_k y_k}$。

不用系数的话,直接套拉格朗日插值公式即可。

 

$\prod\limits_{j \neq i}{\frac{x-j}{i-j}}$ 怎么求?

1. 预处理 $\frac{1}{i!}$,分母就行了。

2. $O(n^2)$ 算出多项式 $s(x)=x(x-1)\dots(x-n)$。

3. 对于 $i=0,1,\dots,n$,$O(n)$ 用长除法算出 $\frac{s(x)}{x-i}$ 即可。

——操作数列

归一化思想。

 

11/30

——数树

重要技巧:长链剖分

所谓长链剖分,就是给每个节点标记一个深度最深的长儿子。

长链剖分的优势:对于可以 $O(1)$ 从一个儿子继承所有所需信息的 dp,用长链剖分,更新时直接 $O(1)$ 继承长儿子的信息,其余儿子的信息暴力继承即可。

时间复杂度 $O(\sum\limits_{u} \sum\limits_{v \in son_u} len_v)=O(n)$,其中 $len_v$ 为 $v$ 所在长链的长度。

那么,什么 dp 可以 $O(1)$ 从一个儿子继承所有所需信息呢?

能想到的只有:维护的信息仅与深度有关的 dp。

例如,$f[u][j]$ 代表与 $u$ 距离为 $j$ 的节点的数量,$g[u][j]$ 代表与 $u$ 距离为 $j$ 的节点的权值和等等。

怎么 $O(1)$ 继承?

维护一个大小为 $O(n)$ 的数组和一个指针,每次来到长链链头 $u$ 时开出一块长为 $len_u$ 的空间给它,即令 $f_u=pos$。

然后对于长儿子 $son_u$,令 $f_{son(u)}=f_u±1$ 即可 $O(1)$ 继承与深度有关的信息。

对于长链剖分,树上差分很有用哦~

——拼接

重要技巧:后缀自动机

后缀自动机是一个神奇的自动机,可以接受一个串 $s$ 的所有子串。

使用后缀自动机时,一定要注意不要按照节点编号顺序逆序乱搞,后缀自动机的边不一定都是从小指向大的。一定要用记忆化搜索/拓扑序逆序!

 

12/2

——人生经验

对于“一个序列是好的,当且仅当它可以通过某种方式转化成某某,求好序列的数量”这种题目,可以定义一个 $f_a(x)$ 为一个序列的函数,然后想办法找到 $f_a(x)$ 与转化方式之间的关系进行递推。

若 $f$ 的值域和定义域都很小,可以分 $f_a(x)$ 在什么情况取什么值进行递推。

 

12/3

——字符串

判断一个字符串的子串是否同构,可以计算 $last_i$ 为位置 $i$ 之前第一个与 $s_i$ 相同的位置,判断子串的 $last$ 序列是否相同。

注意,计算子串 $[l,r]$ 的 $last$ 序列,$last$ 序列只能从后向前计算到 $l$ 为止,因此需要用主席树/可持久化分块数组维护。

重要技巧:可持久化分块数组

其实就是把数组分块,更新时只用拿出一个块进行更新,整块整块的更新每次更新自己去记录它,预处理 $O(n \sqrt{n})$,查询 $O(1)$,由于本题用查询来排序,这样做是 $O(n \log^2 n + n \sqrt{n})$,比用主席树 $O(n \log^3 n)$ 好。

 

12/11

——P5024 [NOIP2018 提高组] 保卫王国

重要技巧:dp 转矩阵优化 -> 树上倍增

有些 dp 转移方程看上去倍增是不好维护,怎么办?把它弄成矩阵的形式,然后利用结合律倍增!

如何判定一个广义矩阵的乘法是否满足结合律?

参考文献:https://www.cnblogs.com/luckyblock/p/14430820.html

简单来说,就是要求广义乘法 $\otimes$ 要对广义加法 $\oplus$ 有分配律,广义乘法 $\otimes$ 有交换律、结合律。

即 $(a \oplus b) \otimes c=(a \otimes c) \oplus (b \otimes c)$ 恒成立。

一个经典的例子就是:$\oplus=\min, \otimes=+$,即 $c(i,j)=\min(a(i,k)+b(k,j))$(当然换成 $\max$ 也是可以的)。

不要再盲推公式了啊啊啊啊

 

posted @ 2021-11-08 22:20  CharlieVinnie  阅读(56)  评论(0编辑  收藏  举报