数据结构
势能分析
注意到一些空间或时间的限制,使得一些 “暴力” 算法时空合法。
比如:
1.一个点最多会被考虑到多少次,从而往启发式的方向思考。
2.在树上把询问离线挂在点上,
d
f
s
dfs
dfs 时直接跑轻儿子统计答案,详见蜘蛛爬树。
3.吉如一线段树
4.多利用树剖重儿子的优秀性质,以及
s
e
t
set
set 的优秀性质,去维护一些加入和修改一一对应的操作。
5.对于若干段需要分开求的答案,但求完之后可以合并的题目,考虑这相当于染色,然后套用
l
c
t
lct
lct 中
a
c
c
e
s
s
access
access 的时间复杂度。
例:
D
T
O
J
4734
(
l
c
t
+
s
a
m
)
DTOJ4734(lct+sam)
DTOJ4734(lct+sam)。
考虑把
s
a
m
sam
sam 建出来,容斥计算答案,对于每次新增的子串
s
[
1
,
i
]
,
s
[
2
,
i
]
…
s
[
l
e
n
i
,
i
]
s[1,i],s[2,i] \dots s[len_i,i]
s[1,i],s[2,i]…s[leni,i],计算有多少子串属于它。
我们这样考虑:如果我们知道每个子串的最后出现位置的左端点,那么我们就能够统计答案。
所以问题转化为如何去维护每个子串的出现位置的左端点的线段树。
显然,更新了位置
i
i
i 之后,子串
s
[
1
,
i
]
,
s
[
2
,
i
]
,
…
,
s
[
i
,
i
]
s[1,i],s[2,i],\dots,s[i,i]
s[1,i],s[2,i],…,s[i,i],的最后出现位置一定是
1
,
2
,
…
,
i
1,2,\dots,i
1,2,…,i,所以对于区间
[
1
,
i
]
[1,i]
[1,i] 加
1
1
1 即可,然后我们还要把一些出现过的子串对应的区间给扣掉,这些子串显然在
p
a
r
e
n
t
parent
parent 树上新增节点的祖先上,暴力跳链去修改的复杂度显然是错的,但是我们注意到一点,当一条链被暴力修改之后,这些子串最后出现位置的右端点一定是
i
i
i,他们的左端点就一定是连续的,这就相当于下次用到这条链时,这若干个点的答案可以统一计算,这像极了什么?没错就是
l
c
t
lct
lct 中的
a
c
c
e
s
s
access
access 操作。然后根据
l
c
t
lct
lct 的复杂度即可证明该复杂度正确,用
l
c
t
lct
lct 的方式去做即可。
6.多注意题目中非常小的变量,从值域/定义域的角度去优化。
例:
D
T
O
J
4919
DTOJ4919
DTOJ4919
考虑去维护每个点的超集,暴力去维护,然后注意到 k k k 非常小,然后转移过程又是 D A G DAG DAG 所以每次检索到的点的出现次数如果大于 m a x { k } max\{k\} max{k} 就返回,这样效率就从 O ( n 2 ) O(n^2) O(n2) 优化到 O ( n k ) O(nk) O(nk) 了。
统计答案
对于一些操作,有些好维护,有些不好直接维护。我们对于不好直接维护的答案考虑从其他角度去维护它。(并不一定需要及时维护)。
例: D T O J 4926 DTOJ4926 DTOJ4926
d
i
s
t
(
i
,
j
)
=
d
j
+
d
i
−
2
×
d
l
c
a
(
i
,
j
)
dist(i,j)=d_j+d_i-2 \times d_{lca(i,j)}
dist(i,j)=dj+di−2×dlca(i,j),对于一个固定的
j
j
j,我们在
d
f
s
dfs
dfs 序上可以很好地维护
d
j
,
d
i
d_j,d_i
dj,di的答案,但是
l
c
a
lca
lca 的贡献不好维护,我们这样考虑:
首先考虑树剖,从
y
y
y 开始往
x
x
x 跳,对于
l
c
a
lca
lca 为
i
i
i 的贡献,我们在询问的时候再去计算这个答案。详细地说,我们询问的时候并不关心是哪些点的答案和,我们只关心总的答案和,又因为该答案在
i
i
i 处好统计,在
j
j
j 处不好统计,所以我们询问时只需要知道又多少重链上的点查询了多少其轻儿子树,维护子树和以及询问时往根节点跳重链即可。
dp转移相关
决策单调性
有些 d p dp dp,当 i < j i<j i<j 且从 j j j 转移比从 i i i 转移更优时, i i i 就不可能成为答案了,也就是说——决策点满足单调性。其中,一个决策点 i i i 的最优区间一定是连续的且不同决策点的区间不重不漏覆盖了 [ 1 , n ] [1,n] [1,n],那么这种时候我们就可以根据其性质去优化。
换而言之,就是去维护 “最优区间”,维护一段一段区间的最优决策点。
查询
i
i
i 的更新时,直接查询
i
i
i 的最优决策点即可,然后把决策点
i
i
i 插入更新最优区间。
我们细说后者,我们根据性质去考虑每一段区间的最优决策点
j
j
j,然后对于
i
i
i 我们可以求出一个分界点
k
k
k,使得
[
1
,
k
]
[1,k]
[1,k] 和
[
k
+
1
,
n
]
[k+1,n]
[k+1,n] 分别有一个更优的点,然后根据此去更新维护最优区间的栈即可。
势能(另)
例: D T O J 5899 DTOJ5899 DTOJ5899
一个朴素的想法是启发式合并,然后暴力遍历出边寻找答案,每条边最多会被加入一次,而会被检查多次,这样显然时间是很浪费的。所以我们要想一个办法,让符合条件的边被检查到或者说,减少一条边被检查到的次数。
重新审视一下我们需要什么:我们需要设置一个选边的标准,使其满足:
1.从点出发遍历。
2.每次需要重新设置,使其在两个端点上都不满足,并且该次数较小(
l
o
g
n
?
n
?
log n? \sqrt n ?
logn?n? )。
因为与两个点有关,判定条件是对于边 e : ( i , j ) e:(i,j) e:(i,j),满足 a i + a j ≥ w e a_i+a_j \geq w_e ai+aj≥we,所以这里有一个经典操作(套路),用 a i + a j a_i+a_j ai+aj 的和与 w e w_e we 的差值的一半加在两个电上作为阈值,即 i i i 处的阈值为 a i + w e − a i − a j 2 a_i+ \frac{w_e-a_i-a_j}{2} ai+2we−ai−aj, j j j 处的阈值为 a j + w e − a i − a j 2 a_j+ \frac{w_e-a_i-a_j}{2} aj+2we−ai−aj。这样每次只有达到阈值才检查该条边,若不满足,则再重新赋值两个点的阈值,这样就又都不满足阈值了,可以证明,最多不满足 l o g log log 次,然后就成功的优化了效率。
暂时就到这里,最近就学了这些东西。faq
接下来准备做的事:
1.学圆方树
2.复习
a
c
ac
ac 自动机,马拉车。
3.一天一道数据结构(题单
o
r
or
or 洛谷)
4.生成函数(优先级最低)
5.网络流刷题
6.每天
3
3
3 道测试题一定补完。
7.每天
12
12
12 点前一定睡觉。
暂时就这些。 希望不要咕了