forked from EndlessCheng/codeforces-go
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathdp.go
3032 lines (2843 loc) · 112 KB
/
dp.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
package copypasta
import (
"container/heap"
"math"
"math/bits"
"sort"
)
/* 动态规划
入门视频:https://www.bilibili.com/video/BV1Xj411K7oF/
思考过程:
1. 把原问题重新复述一遍,例如「从前 n 个数中选择若干个数,这些数的和为 m 的方案数」。
2. 根据题意,尝试「缩小」问题的规模,我们可以怎样缩小?
- 这里有两个变量 n 和 m,有什么方法可以把它们缩小?
3. 尝试「原子」操作(考虑其中「一个」数选或者不选,例如第 n 个数):
- 不选第 n 个数,问题变为「从前 n-1 个数中选择若干个数,这些数的和为 m 的方案数」。
- 选第 n 个数,问题变为「从前 n-1 个数中选择若干个数,这些数的和为 m-a[n] 的方案数」。
- 原问题可以不重不漏地分解成这两种情况。
- 根据加法原理,原问题为这两种方案的和。
4. 这可以用记忆化搜索写。终点是什么?
- n=0。(这里数组下标是从 1 开始的)
5. 如果用递推来思考,要怎么写?空间能否压缩?
- 自底向上思考记忆化搜索的过程。
6.(进阶)如果复杂度过高,如何根据状态转移方程来优化?
7.(进阶)状态不好确定时,尝试转化问题模型、逆序思考、增加维度等等。(试试下面的题目)
题目已经分类整理好:试试搜索「线性」「最大子段和」等。
如何设计状态
http://codeforces.com/problemset/problem/14/E
https://codeforces.com/problemset/problem/360/B
https://codeforces.com/problemset/problem/461/B
https://codeforces.com/problemset/problem/553/A
https://codeforces.com/problemset/problem/571/B
https://codeforces.com/problemset/problem/687/C
https://codeforces.com/problemset/problem/744/C
https://codeforces.com/problemset/problem/1012/C
https://codeforces.com/problemset/problem/1025/D
https://codeforces.com/problemset/problem/1027/E
https://codeforces.com/problemset/problem/1286/A
https://codeforces.com/problemset/problem/1408/D
https://codeforces.com/problemset/problem/1783/D 推公式
https://atcoder.jp/contests/abc237/tasks/abc237_f
SEERC05,紫书例题 9-3,UVa 1347 https://onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&category=446&page=show_problem&problem=4093
Daejeon11,紫书例题 9-8,UVa 1625 https://onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&category=825&page=show_problem&problem=4500
LC956 https://leetcode-cn.com/problems/tallest-billboard/
涉及到相邻状态先后关系的 DP(喂兔子)https://codeforces.com/problemset/problem/358/D
戳气球 LC312 https://leetcode-cn.com/problems/burst-balloons/
消消乐 LC546 https://leetcode-cn.com/problems/remove-boxes/ https://leetcode.com/contest/leetcode-weekly-contest-25
混合逆序对 https://atcoder.jp/contests/arc097/tasks/arc097_c
寻找子问题 https://atcoder.jp/contests/arc116/tasks/arc116_d
https://codeforces.com/contest/1579/problem/G
todo https://atcoder.jp/contests/abc200/tasks/abc200_e
DI 序列的有效排列 LC903 https://leetcode.cn/problems/valid-permutations-for-di-sequence/
决策单调性
https://codeforces.com/problemset/problem/229/D
增量法
LC2262 https://leetcode.cn/problems/total-appeal-of-a-string/
LC828 https://leetcode.cn/problems/count-unique-characters-of-all-substrings-of-a-given-string/
https://codeforces.com/problemset/problem/1428/F
思维转换
谁来当 DP 对象 LC1434 https://leetcode-cn.com/problems/number-of-ways-to-wear-different-hats-to-each-other/
扔蛋问题 LC887 https://leetcode-cn.com/problems/super-egg-drop/ https://www.bilibili.com/video/BV1KE41137PK
LC920* https://leetcode-cn.com/problems/number-of-music-playlists/ 注:官方题解给出了一种生成函数的做法
状态优化 https://codeforces.com/problemset/problem/838/E
「排序」题的转换 https://codeforces.com/problemset/problem/1223/D
https://codeforces.com/problemset/problem/1542/D
https://codeforces.com/problemset/problem/520/E
https://codeforces.com/problemset/problem/883/I
路径计数+推箱子 https://codeforces.com/problemset/problem/1225/E
找关键元素+状态机DP https://codeforces.com/problemset/problem/623/B
https://codeforces.com/problemset/problem/1624/E
NOTE: 无后效性是指当前的决策只与过去的结果有关,而与过去的决策无关
NOTE: 若状态转移不构成 DAG,请尝试建图+BFS,见:
https://ac.nowcoder.com/acm/contest/6218/B
https://codeforces.com/problemset/problem/283/B 活用 012 染色
NOTE: 若使用滚动数组,注意复用时可能要初始化
NOTE:(区间 DP)正向计算不易时,试着反向计算
TIPS: 若转移是若干相邻项之和,可以考虑 f(p) - f(p-1) 的值,用滑动窗口来维护区间和,从而优化转移
例题 LC837 https://leetcode-cn.com/problems/new-21-game/
递归打印路径:https://codeforces.com/problemset/problem/2/B
需要补充额外的状态 https://codeforces.com/problemset/problem/682/D
todo Non-trivial DP Tricks and Techniques https://codeforces.com/blog/entry/47764
交替 DP
https://codeforces.com/problemset/problem/1479/B2
思路二 https://www.luogu.com.cn/blog/wsyhb/post-ti-xie-cf1479b2-painting-the-array-ii
计数 DP
另见 math.go 中的「一些组合问题」
入门计数 DP https://atcoder.jp/contests/abc248/tasks/abc248_c
多重组合
- 见挑战
多重排列
- dp[i][j] 表示前 i 类数字组成长为 j 的排列个数
- dp[i][j] = ∑dp[i-1][k]*C(j,k), 0<=k<=min(j,cnt[i])
- 边界 dp[0][0] = 1
todo https://atcoder.jp/contests/abc234/tasks/abc234_f
带约束的计数 DP https://codeforces.com/problemset/problem/1767/C
https://codeforces.com/problemset/problem/1794/D
贪心优化 DP
https://codeforces.com/problemset/problem/864/E
双指针优化 DP
https://codeforces.com/problemset/problem/883/I
https://training.olinfo.it/#/task/preoii_yutaka/statement
参考书籍推荐:
《算法竞赛进阶指南》- 介绍了大量且全面的 DP 内容,是目前市面上讲解 DP 最好的一本书
视频讲解:
https://www.bilibili.com/video/BV1gf4y1i78H 动态规划的套路 by wisdompeak
https://www.bilibili.com/video/av70148899 DP 入门,01 背包,完全背包,多重背包
https://www.bilibili.com/video/av77393700 LCS LIS
https://www.bilibili.com/video/av83939419 区间 DP
https://www.bilibili.com/video/av93356551 状态压缩 DP
https://www.bilibili.com/video/av98090640 树形 DP
https://www.bilibili.com/video/av85636122 动态规划 · 零 - Introduction
https://www.bilibili.com/video/av86983419 动态规划 · 一 - 序列型
https://www.bilibili.com/video/av89052674 动态规划 · 二 - 坐标、双序列、划分 & 状态压缩
套题/总结:
推荐 AtCoder 上的经典 DP 场 https://atcoder.jp/contests/dp
题解 https://www.cnblogs.com/shanxieng/p/10232228.html
https://codeforces.com/blog/entry/92170
https://www.hamayanhamayan.com/entry/2019/01/12/163853
讨论 https://codeforces.com/blog/entry/64250
《挑战程序设计竞赛》上的练习题(均为 POJ)
2.3 节
3176 https://www.luogu.com.cn/problem/P1216 数字三角形
2229 https://www.luogu.com.cn/problem/P6065 将 n 分拆为若干个 2 的次幂的和的方法数 https://oeis.org/A018819
2385 https://www.luogu.com.cn/problem/P2690 dp[i分钟][j移动次数] = max(dp[i-1][j], dp[i-1][j-1]) + 当前分钟是否有苹果落在 j 次移动后的位置 最后答案为 max{dp[n-1]}
3616 https://www.luogu.com.cn/problem/P2889 DAG 最长路
3280 https://www.luogu.com.cn/problem/P2890 增删取 min,跑区间 DP
1742 http://acm.hdu.edu.cn/showproblem.php?pid=2844 多重背包
3046 http://poj.org/problem?id=3046 todo
3181 https://www.luogu.com.cn/problem/P6205 完全背包
1065 http://acm.hdu.edu.cn/showproblem.php?pid=1051 n 轮 LIS
1631 http://acm.hdu.edu.cn/showproblem.php?pid=1950 转换成 LIS
3666 https://www.luogu.com.cn/problem/P2893
https://codeforces.com/problemset/problem/13/C
https://codeforces.com/problemset/problem/713/C
https://www.luogu.com.cn/problem/P4597 加强版
2392 https://www.luogu.com.cn/problem/P6771 多重背包,按高度限制排序。高度既是价值也是体积
2184 https://www.luogu.com.cn/problem/P2340 把 IQ 看成体积,EQ 看成价值,注意把负数偏移到非负数,以及负数的转移写法
todo 3.4 节
2686 https://www.luogu.com.cn/problem/SP1700
1769 https://www.luogu.com.cn/problem/SP90 https://www.luogu.com.cn/problem/UVA1322
2441
3254 https://www.luogu.com.cn/problem/P1879
2836
1795 https://www.luogu.com.cn/problem/SP1776
3411 https://www.luogu.com.cn/problem/SP3953
3420
3735
3171 https://www.luogu.com.cn/problem/P4644 见 graph.shortestPathDijkstra
CSES DP section editorial https://codeforces.com/blog/entry/70018
力扣上的 DP 问题
分类汇总 https://zhuanlan.zhihu.com/p/126546914
https://leetcode.com/discuss/general-discussion/458695/dynamic-programming-patterns
https://github.com/CyC2018/CS-Notes/blob/master/notes/Leetcode%20%E9%A2%98%E8%A7%A3%20-%20%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92.md
https://leetcode.com/problems/best-time-to-buy-and-sell-stock-with-transaction-fee/discuss/108870/Most-consistent-ways-of-dealing-with-the-series-of-stock-problems
https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-iv/solution/yi-ge-tong-yong-fang-fa-tuan-mie-6-dao-gu-piao-w-5/
https://leetcode-cn.com/tag/dynamic-programming/
信息学奥赛一本通 第二部分 基础算法 --> 第九章 动态规划 http://ybt.ssoier.cn:8088/index.php
算法竞赛专题解析(11):DP概述和常见DP面试题 https://blog.csdn.net/weixin_43914593/article/details/105444090
todo 题目推荐 https://www.luogu.com.cn/blog/wyy2020/dp-qian-tan
https://www.cnblogs.com/flashhu/p/9480669.html
其他资料:
https://github.com/hzwer/shareOI/tree/master/%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92
https://oi-wiki.org/dp/
https://cp-algorithms.com/dynamic_programming/divide-and-conquer-dp.html
https://wenku.baidu.com/view/7c9de809581b6bd97f19ea72.html 算法合集之《从《鹰蛋》一题浅析对动态规划算法的优化》
*/
func _(min, max func(int, int) int, abs func(int) int) {
// 涉及到前缀和/子数组和的问题
// 定义 dp[i] 表示前缀 a[:i] 中子数组和为 targetSum 的最短子数组长度
// 下面的代码来自 LC1477 https://leetcode-cn.com/problems/find-two-non-overlapping-sub-arrays-each-with-target-sum/
prefixSumDP := func(a []int, targetSum int) int {
n := len(a)
const inf int = 1e9
ans := inf
dp := make([]int, n+1)
for _i := range dp {
dp[_i] = inf
}
preSumPos := map[int]int{0: -1} // int64
sum := 0
for i, v := range a {
dp[i+1] = dp[i]
sum += v
if p, ok := preSumPos[sum-targetSum]; ok {
// sum_[p+1,i] == targetSum
l := i - p
if dp[p+1] < inf {
ans = min(ans, dp[p+1]+l)
}
dp[i+1] = min(dp[i+1], l)
}
preSumPos[sum] = i
}
if ans == inf {
ans = -1
}
return ans
}
// 由于数据范围的原因,采用 map 记忆化 dpMap
// https://codeforces.com/problemset/problem/510/D
// https://codeforces.com/problemset/problem/1746/D
// 如何估计时间复杂度 https://atcoder.jp/contests/abc275/tasks/abc275_d
mapDP := func(n int) {
{
// 一维(多维见下)
dp := map[int]int{}
var f func(int) int
f = func(x int) (res int) {
//if x == 0 {
// return
//}
if v, ok := dp[x]; ok {
return v
}
defer func() { dp[x] = res }()
return
}
f(n)
}
{
// 多维
type pair struct{ x, y int }
dp := map[pair]int{}
var f func(int, int) int
f = func(x, y int) (res int) {
//if x == n {
// return
//}
p := pair{x, y}
if v, ok := dp[p]; ok {
return v
}
defer func() { dp[p] = res }()
return
}
f(0, 0)
}
}
/* 线性 DP
① 前缀/后缀之间的转移,例如从 dp[i-1] 转移到 dp[i],或者从 dp[j] 转移到 dp[i]
LC70 https://leetcode.cn/problems/climbing-stairs/
LC746 https://leetcode.cn/problems/min-cost-climbing-stairs/
LC198 https://leetcode.cn/problems/house-robber/
- 变形:恰好选 floor(n/2) 个 https://atcoder.jp/contests/abc162/tasks/abc162_f
LC213 https://leetcode.cn/problems/house-robber-ii/
- 相似题目 https://atcoder.jp/contests/abc251/tasks/abc251_e
LC276 https://leetcode.cn/problems/paint-fence/
LC343 https://leetcode.cn/problems/integer-break/
LC368 https://leetcode.cn/problems/largest-divisible-subset/
LC1105 https://leetcode.cn/problems/filling-bookcase-shelves/
LC1416 https://leetcode.cn/problems/restore-the-array/
LC2369 https://leetcode.cn/problems/check-if-there-is-a-valid-partition-for-the-array/
- 相似题目 https://codeforces.com/problemset/problem/1624/E
LC2547 https://leetcode.cn/problems/minimum-cost-to-split-an-array/
另见 LIS
② 双序列问题,一般定义 dp[i][j] 表示对子问题 (s1[:i],s2[:j]) 的求解结果
LC727 https://leetcode.cn/problems/minimum-window-subsequence/
LC983 https://leetcode.cn/problems/minimum-cost-for-tickets/
LC1639 https://leetcode.cn/problems/number-of-ways-to-form-a-target-string-given-a-dictionary/
另见 LCS LPS
③ 多维 / 额外状态
LC123 https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-iii/
LC188 https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-iv/
LC309 https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-with-cooldown/
LC920 https://leetcode-cn.com/problems/number-of-music-playlists/
LC956 https://leetcode-cn.com/problems/tallest-billboard/
LC1186 https://leetcode.cn/problems/maximum-subarray-sum-with-one-deletion/
LC1223【推荐】https://leetcode.cn/problems/dice-roll-simulation/
LC1477 https://leetcode-cn.com/problems/find-two-non-overlapping-sub-arrays-each-with-target-sum/
LC1531 看起来是区间 DP,仔细分析后是线性 DP https://leetcode-cn.com/problems/string-compression-ii/
LC2209 https://leetcode.cn/problems/minimum-white-tiles-after-covering-with-carpets/
入门 DP:跳台阶+禁入点 https://atcoder.jp/contests/abc289/tasks/abc289_d
入门计数 DP https://atcoder.jp/contests/abc248/tasks/abc248_c
选或不选 [1800·hot10] https://codeforces.com/contest/1525/problem/D
https://codeforces.com/contest/1324/problem/E
https://codeforces.com/problemset/problem/505/C
https://atcoder.jp/contests/abc267/tasks/abc267_d
贪心+abs https://atcoder.jp/contests/abc163/tasks/abc163_e
由 n 个值互不相同的点组成的高度不小于 h 的 BST 有多少个 https://codeforces.com/problemset/problem/9/D
https://codeforces.com/problemset/problem/38/E
好题:涉及到相邻状态先后关系的 DP(喂兔子) https://codeforces.com/problemset/problem/358/D
https://codeforces.com/problemset/problem/446/A
https://codeforces.com/problemset/problem/603/A
处理区间元素不能在区间外面的技巧 https://codeforces.com/problemset/problem/811/C https://codeforces.com/contest/811/submission/174568255
https://codeforces.com/problemset/problem/1120/C
与 KMP 结合 https://codeforces.com/problemset/problem/1163/D
https://codeforces.com/problemset/problem/1168/C
https://codeforces.com/problemset/problem/1542/D
不相交区间 DP
LC2008 https://leetcode.cn/problems/maximum-earnings-from-taxi/
LC1235 https://leetcode.cn/problems/maximum-profit-in-job-scheduling/
https://codeforces.com/problemset/problem/1801/C
排列型/插入型
LC629 https://leetcode.cn/problems/k-inverse-pairs-array/ https://www.luogu.com.cn/problem/P2513
https://www.lanqiao.cn/problems/240/learning/
https://atcoder.jp/contests/abc282/tasks/abc282_g
*/
// 网格路径问题
// LC62 https://leetcode.cn/problems/unique-paths/
// LC63 https://leetcode.cn/problems/unique-paths-ii/
// LC64 https://leetcode.cn/problems/minimum-path-sum/
// - 变形:连续性 & 上下界思想 https://codeforces.com/contest/1695/problem/C
// LC120 https://leetcode.cn/problems/triangle/ https://www.luogu.com.cn/problem/P1216
// LC931 https://leetcode.cn/problems/minimum-falling-path-sum/
// LC2435 https://leetcode.cn/problems/paths-in-matrix-whose-sum-is-divisible-by-k/
// LC2684 https://leetcode.cn/problems/maximum-number-of-moves-in-a-grid/
// 每行至多选三个 https://atcoder.jp/contests/abc175/tasks/abc175_e
// 转换 https://codeforces.com/problemset/problem/1695/C
// 最大子段和 LC53 https://leetcode.cn/problems/maximum-subarray/ https://www.luogu.com.cn/problem/P1115
// 有两种思路
// 1. 定义状态 dp[i] 表示以 a[i] 结尾的最大子段和,则有状态转移方程 dp[i]=max(dp[i−1],0)+a[i],答案为 max(dp)
// 2. 遍历 a 的同时维护前缀和的最小值,则遍历到 a[i] 时,当前最大子段和为 sum[i]-min(sum[j]), j<i
// 算法导论 练习4.1-5
// [题型总结] 关于最大子段和及其变式 https://www.luogu.com.cn/blog/wey-yzyl/zui-tai-zi-duan-hu-ji-ji-bian-shi-di-qi-shi
// 子段长度有上限的最大子段和:见单调队列,题目为 https://ac.nowcoder.com/acm/contest/1006/D
// 子段长度有下限的最大子段和:转换为前缀和之差 sum[i]-sum[j],i-j>=K,维护 mn=min(mn,sum[j]),同时更新 sum[i]-mn 的最大值(题目见 sort.go 中的 0-1 分数规划)https://www.luogu.com.cn/problem/P1404
// - 等价题目:把 k 个数增加 x,n-k 个数减少 x https://codeforces.com/problemset/problem/1796/D
// 子段和有上限的最大子段和:转换为前缀和之差 sum[i]-sum[j]<=K,在平衡树上二分 sum[j] LC363 https://leetcode-cn.com/problems/max-sum-of-rectangle-no-larger-than-k/
// 最大两段子段和:求每个位置上的前缀最大子段和和后缀最大子段和 https://www.luogu.com.cn/problem/P2642
// - 等价题目:允许翻转一段子区间的最大子段和
// 删除至多一个元素后的最大子段和 LC1186 https://leetcode.cn/problems/maximum-subarray-sum-with-one-deletion/
// 最大 m 段子段和 http://acm.hdu.edu.cn/showproblem.php?pid=1024
// 环状最大子段和:转换为 max(最大子段和, 总和减去最小子段和) LC918 https://leetcode-cn.com/problems/maximum-sum-circular-subarray/
// 环状最大两段子段和:思路类似,注意取反后需要传入 a[1:n-1] https://www.luogu.com.cn/problem/P1121 https://ac.nowcoder.com/acm/contest/7738/B
// 去掉一个最大值的最大子段和(值域比较小)https://codeforces.com/contest/1359/problem/D
// 变形题 https://codeforces.com/problemset/problem/33/C
// https://codeforces.com/problemset/problem/788/A
// https://codeforces.com/problemset/problem/1155/D
// https://codeforces.com/problemset/problem/1197/D 思路 https://docs.qq.com/sheet/DWGFoRGVZRmxNaXFz 里面搜本题链接
// https://codeforces.com/problemset/problem/1373/D
// 需要一些转换技巧 https://codeforces.com/problemset/problem/1082/E
// 多个小数组合并 https://codeforces.com/problemset/problem/75/D
// 这题做法需要用到上面说到的第二种思路
// 二维的情况(最大子阵和)可以枚举上下边界,转换成一维 O(n^3)
maxSubarraySum := func(a []int) int {
if len(a) == 0 { // 根据题意返回
return 0
}
maxS, sum := a[0], a[0] // int64
for _, v := range a[1:] {
sum = max(sum, 0) + v
maxS = max(maxS, sum)
}
if maxS < 0 { // 根据题意返回
//return 0
}
return maxS
}
// 除了返回最大子段和外,还返回最大子段和对应的子段 [l,r]
// https://codeforces.com/contest/1692/problem/H
maxSubarraySumWithRange := func(a []int) (maxS, l, r int) {
if len(a) == 0 { // 根据题意返回
return 0, -1, -1
}
// int64
maxS = a[0] // 注意 l 和 r 默认为 0,即 a[:1]
for i, sum, st := 1, a[0], 0; i < len(a); i++ {
if sum < 0 {
sum, st = 0, i // 重新开始
}
sum += a[i]
if sum > maxS {
maxS, l, r = sum, st, i
}
}
if maxS < 0 { // 根据题意返回
//return 0, -1, -1
}
return
}
// 维护前缀和的最小值的写法
// https://codeforces.com/contest/1692/problem/H
maxSubarraySumWithRange = func(a []int) (maxS, l, r int) {
if len(a) == 0 { // 根据题意返回
return 0, -1, -1
}
// int64
maxS = a[0] // 注意 l 和 r 默认为 0,即 a[:1]
sum, minS, minI := 0, 0, -1
for i, v := range a {
sum += v
if sum-minS > maxS {
maxS, l, r = sum-minS, minI+1, i
}
if sum < minS {
minS, minI = sum, i
}
}
if maxS < 0 { // 根据题意返回
//return 0, -1, -1
}
return
}
// 最大两段子段和(两段必须间隔至少 gap 个数)
maxTwoSubarraySum := func(a []int, gap int) int {
// 注意下界
n := len(a)
suf := make([]int, n) // int64
suf[n-1] = a[n-1]
curSum := a[n-1]
for i := n - 2; i >= 0; i-- {
v := a[i]
curSum = max(curSum+v, v)
suf[i] = max(suf[i+1], curSum)
}
curSum, pre := a[0], a[0]
ans := pre + suf[1+gap]
for i := 1; i < n-1-gap; i++ {
v := a[i]
curSum = max(curSum+v, v)
pre = max(pre, curSum)
ans = max(ans, pre+suf[i+1+gap])
}
return ans
}
maxSubarrayAbsSum := func(a []int) int {
if len(a) == 0 {
return 0
}
//min, max, abs := math.Min, math.Max, math.Abs
curMaxSum, maxSum := a[0], a[0]
curMinSum, minSum := a[0], a[0]
for _, v := range a[1:] {
curMaxSum = max(curMaxSum+v, v)
maxSum = max(maxSum, curMaxSum)
curMinSum = min(curMinSum+v, v)
minSum = min(minSum, curMinSum)
}
return max(abs(maxSum), abs(minSum))
}
// 最大子序列交替和(买卖股票)
// 有两种思路:
// 1. 动态规划,具体见我的题解 https://leetcode-cn.com/problems/maximum-alternating-subsequence-sum/solution/dong-tai-gui-hua-by-endlesscheng-d92a/
// 2. 贪心,由于第一个值需要取正,将开头补上 0,就变成买卖股票问题了,只需关心波峰和波谷的值,即 ∑max(0,a[i+1]-a[i])
// LC1911 https://leetcode-cn.com/problems/maximum-alternating-subsequence-sum/
// LC122 https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-ii/
// 扩展:O(1) 回答交换其中两个元素后的最大子序列交替和 https://codeforces.com/problemset/problem/1420/C2
maxAlternatingSumDP := func(a []int) int {
dp := [2]int{0, -1e9} // int64
for _, v := range a {
dp = [2]int{max(dp[0], dp[1]-v), max(dp[1], dp[0]+v)}
}
return dp[1]
}
maxAlternatingSumGreedy := func(a []int) (ans int) {
a = append([]int{0}, a...)
for i := 1; i < len(a); i++ {
ans += max(0, a[i]-a[i-1]) // int64
}
return
}
// 修改序列为非降或非增的最小修改次数
// - 单次修改可以把某个数 +1 或 -1
// 通过一个例子来解释这个基于堆的算法:1 5 10 4 2 2 2 2
// 假设当前维护的是非降序列,前三个数直接插入,不需要任何修改
// 插入 4 的时候,可以修改为 1 5 5 5,或 1 5 6 6,或... 1 5 10 10,修改次数均为 6
// 但我们也可以把修改后的序列视作 1 5 4 4,虽然序列不为非降序列,但修改的次数仍然为 6
// 接下来插入 2,基于 1 5 5 5 的话,修改后的序列就是 1 5 5 5 5,总的修改次数为 9
// 但我们也可以把修改后的序列视作 1 2 4 4 2,总的修改次数仍然为 9
// 接下来插入 2,如果基于 1 5 5 5 5 变成 1 5 5 5 5 5,会得到错误的修改次数 12
// 但是实际上有更优的修改 1 4 4 4 4 4,总的修改次数为 11
// 同上,把这个序列视作 1 2 2 4 2 2,总的修改次数仍然为 11
// ...
// 其他解释见 https://leetcode.cn/problems/make-array-non-decreasing-or-non-increasing/solution/by-gittauros-6x9v/
// https://codeforces.com/problemset/problem/13/C
// https://www.luogu.com.cn/problem/P4597
// LC2263 https://leetcode.cn/problems/make-array-non-decreasing-or-non-increasing/
// https://www.luogu.com.cn/problem/P2893
// http://poj.org/problem?id=3666
// https://codeforces.com/problemset/problem/713/C 严格单调递增 https://codeforces.com/blog/entry/47094?#comment-315161
// 这道题做了一个 a[i]-=i 的操作(i 从 1 开始),把严格单调递增变成了非降的情况,从而可以应用该算法
// 这一技巧的原理是,对于整数来说,单调递增的最小情况是 y=x+C,减去这一函数,就得到了非降序列的最小情况 y=C
minCostSorted := func(a []int) int64 {
h := hp{} // 大根堆
ans := int64(0)
for _, v := range a {
h.push(v)
if d := h.IntSlice[0] - v; d > 0 {
ans += int64(d)
h.IntSlice[0] = v
heap.Fix(&h, 0)
}
}
return ans
}
// 最长公共子序列 (LCS)
// 有向无环图:s1[i] == s2[j] (i-1,j-1) -> (i,j) $ 1
// s1[i] != s2[j] (i-1,j) -> (i,j) $ 0
// (i,j-1) -> (i,j) $ 0
// 更快的做法(位运算)见 SPOJ LCS0 https://www.luogu.com.cn/problem/SP12076
//
// 模板题 LC1143 https://leetcode-cn.com/problems/longest-common-subsequence/
// EXTRA: 最短公共超序列 (SCS) LC1092 https://leetcode-cn.com/problems/shortest-common-supersequence/
// 变种 LC72 https://leetcode-cn.com/problems/edit-distance/
// LC97 https://leetcode-cn.com/problems/interleaving-string/
// LC115 https://leetcode-cn.com/problems/distinct-subsequences/
// LC583 https://leetcode-cn.com/problems/delete-operation-for-two-strings/
// LC712 https://leetcode-cn.com/problems/minimum-ascii-delete-sum-for-two-strings/
// LC1035 https://leetcode-cn.com/problems/uncrossed-lines/
// LC1312 https://leetcode-cn.com/problems/minimum-insertion-steps-to-make-a-string-palindrome/ https://www.luogu.com.cn/problem/P1435
// LC1458 https://leetcode.cn/problems/max-dot-product-of-two-subsequences/
// 权值 https://atcoder.jp/contests/abc185/tasks/abc185_e
// 其中一个改为子串 https://codeforces.com/problemset/problem/163/A
// https://codeforces.com/problemset/problem/1446/B
//【相同子序列个数】https://atcoder.jp/contests/abc130/tasks/abc130_e
// 多个排列的 LCS(转化成 DAG 最长路)https://codeforces.com/problemset/problem/463/D
// 转换【巧妙】https://codeforces.com/problemset/problem/1114/D
// 20多校第二场 https://acm.hdu.edu.cn/showproblem.php?pid=6774
// 与 KMP 结合 https://codeforces.com/problemset/problem/346/B
// 若其中一个序列无重复元素,可以转换成 LIS https://www.luogu.com.cn/problem/P1439 LC1713 https://leetcode-cn.com/problems/minimum-operations-to-make-a-subsequence/
lcs := func(s, t []byte) int {
// dp[i][j] = LCS(s[:i], t[:j])
n, m := len(s), len(t)
dp := make([][]int, n+1)
for i := range dp {
dp[i] = make([]int, m+1)
}
for i, v := range s {
for j, w := range t {
if v == w {
// ignore values from dp[i][j+1] and dp[i+1][j]
dp[i+1][j+1] = dp[i][j] + 1
} else {
dp[i+1][j+1] = max(dp[i][j+1], dp[i+1][j])
}
}
}
{
// EXTRA: 某些 dp 非单调性的题目需要计算全局最值
allMax := 0
for _, row := range dp {
for _, v := range row {
allMax = max(allMax, v)
}
}
}
return dp[n][m]
}
lcsPath := func(s, t []byte) []byte {
n, m := len(s), len(t)
dp := make([][]int, n+1)
for i := range dp {
dp[i] = make([]int, m+1)
}
fa := make([][]int8, n+1)
for i := range fa {
fa[i] = make([]int8, m+1)
}
for i, v := range s {
for j, w := range t {
if v == w {
dp[i+1][j+1] = dp[i][j] + 1
fa[i+1][j+1] = 1
} else {
if dp[i][j+1] > dp[i+1][j] {
dp[i+1][j+1] = dp[i][j+1]
fa[i+1][j+1] = 2
} else {
dp[i+1][j+1] = dp[i+1][j]
fa[i+1][j+1] = 3
}
}
}
}
lcs := make([]byte, 0, dp[n][m])
var makeLCS func(i, j int)
makeLCS = func(i, j int) {
if i == 0 || j == 0 {
return
}
if fa[i][j] == 1 {
makeLCS(i-1, j-1)
lcs = append(lcs, s[i-1])
} else if fa[i][j] == 2 {
makeLCS(i-1, j)
} else {
makeLCS(i, j-1)
}
}
makeLCS(n, m)
return lcs
}
// 最长回文子序列 (LPS)
// 即 LCS(s, reverse(s))
// 视频讲解 https://www.bilibili.com/video/BV1Gs4y1E7EU/
// 回文子串见下面的 isPalindrome 或者 strings.go 的 manacher
// LC516 https://leetcode-cn.com/problems/longest-palindromic-subsequence/
// LC1216 https://leetcode-cn.com/problems/valid-palindrome-iii/
// LC1246 https://leetcode.cn/problems/palindrome-removal/
// 树上路径 LPS https://codeforces.com/problemset/problem/1771/D
longestPalindromeSubsequence := func(s string) int {
n := len(s)
dp := make([][]int, n)
for i := range dp {
dp[i] = make([]int, n)
}
for i := n - 1; i >= 0; i-- {
dp[i][i] = 1
for j := i + 1; j < n; j++ {
if s[i] == s[j] {
dp[i][j] = dp[i+1][j-1] + 2
} else {
dp[i][j] = max(dp[i+1][j], dp[i][j-1])
}
}
}
return dp[0][n-1]
}
// 最长上升子序列 (LIS)
// 视频讲解:https://www.bilibili.com/video/BV1ub411Q7sB/
// 这种写法适用于一些定义比较复杂的变形题
// O(n^2) - 定义 dp[i] 为以 a[i] 为末尾的 LIS 的长度
// 可以把此问题想象成一个「跳跃游戏」,任选一个初始位置向右跳跃,每次只能跳到比当前位置更高的位置,问最多能跳多少次(最后答案加一)
// 这样能更容易地看出转移的顺序,然后变成一个 DAG 上求最长路的问题
// 转换 http://acm.hdu.edu.cn/showproblem.php?pid=1950
// 转换 https://codeforces.com/problemset/problem/1562/E
// 变体 https://codeforces.com/problemset/problem/1350/B
//【网络流 24 题】能取出多少个长为 len(LIS) 的不相交子序列 https://loj.ac/p/6005 https://www.luogu.com.cn/problem/P2766
lisSlow := func(a []int) (ans int) {
n := len(a)
dp := make([]int, n)
for i, v := range a {
dp[i] = 1
for j, w := range a[:i] {
if w < v { // 改成 <= 为非降
dp[i] = max(dp[i], dp[j]+1)
}
}
ans = max(ans, dp[i])
}
return
}
// 最长上升子序列 (LIS)
// 视频讲解:https://www.bilibili.com/video/BV1ub411Q7sB/
// 方法一:二分
// O(nlogn) - 定义 g[i] 为长度为 i+1 的上升子序列的末尾元素的最小值(技巧:交换 O(n^2) 定义中的状态与状态值)
// 求下降,可以考虑取相反数
// https://oi-wiki.org/dp/basic/#_12
// 最小划分数 / 狄尔沃斯定理(Dilworth's theorem)https://en.wikipedia.org/wiki/Dilworth%27s_theorem
// 偏序集的最少反链划分数等于最长链的长度
// 随机排列 LIS 的长度期望 https://www.zhihu.com/question/266958886
// On Range LIS Queries https://codeforces.com/blog/entry/111625 https://codeforces.com/blog/entry/111807 https://arxiv.org/pdf/0707.3619
//
// LC300 https://leetcode.cn/problems/longest-increasing-subsequence/
// LC1964 https://leetcode.cn/problems/find-the-longest-valid-obstacle-course-at-each-position/
// 建模 https://codeforces.com/problemset/problem/269/B
// 经典转换(最多相交问题) https://codeforces.com/problemset/problem/67/D https://atcoder.jp/contests/arc126/tasks/arc126_b
// 最小划分数(导弹拦截)https://www.luogu.com.cn/problem/P1020
// 转化成最小划分数+打印划分方案 https://codeforces.com/problemset/problem/1296/E2
// 合唱队形 https://www.luogu.com.cn/problem/P1091
// 合唱队形(至少有升有降)LC1671 https://leetcode-cn.com/problems/minimum-number-of-removals-to-make-mountain-array/
// 二维 LIS LC354 https://leetcode-cn.com/problems/russian-doll-envelopes/
// 二维 LIS + 打印方案 http://codeforces.com/problemset/problem/4/D
// 将所有元素分成三类:不在任何 LIS / 在至少一个 LIS / 在所有 LIS https://codeforces.com/problemset/problem/486/E
// 重复 T 次的 LIS 问题 https://codeforces.com/problemset/problem/582/B
// 若其中一个序列无重复元素,LCS 可以转换成 LIS https://www.luogu.com.cn/problem/P1439 LC1713 https://leetcode-cn.com/problems/minimum-operations-to-make-a-subsequence/
// 在一维 LIS 的基础上,a[i] 可以从多个数中选一个,问 LIS 最长可以多长
// - 思路:将各个 a[i] 的可选项从大到小排序,然后拼接成一个序列,求 LIS 即可(关键:从大到小排序避免了在同一个可选项中选择多个元素)
// 图上的路径的 LIS https://codeforces.com/problemset/problem/960/F
// LaIS 与单调栈结合 https://codeforces.com/problemset/problem/1468/A
// 状态设计 LIS 计数 https://atcoder.jp/contests/abc237/tasks/abc237_f
// 逆向题:输入 LIS 返回字典序最小的排列 a https://atcoder.jp/contests/arc125/tasks/arc125_c
// bitset 优化 https://codeforces.com/contest/1826/problem/E
lis := func(a []int) int {
g := []int{}
for _, v := range a {
p := sort.SearchInts(g, v) // 改成 v+1 为非严格递增(即 upper_bound)
if p < len(g) {
g[p] = v
} else {
g = append(g, v)
}
}
return len(g)
}
// 方法二:线段树优化 DP
// 在值域上建一棵线段树,单点维护的是 a[i] 对应的 dp 值,区间维护的就是一段值域的 dp 的最大值
// 转移时,查询 < a[i] 的最大值,单点更新到线段树的 a[i] 上
// 这种做法也可以做到 O(nlogn),且更加灵活
// https://www.acwing.com/problem/content/description/3665/
// LC2407 https://leetcode.cn/problems/longest-increasing-subsequence-ii/
// 方法三:平衡树
// todo 参考 https://leetcode.cn/problems/longest-increasing-subsequence-ii/solution/jianjie-by-xing-chen-26-ydqp/
// 方法四:分治 + 单调队列
// todo 参考 https://leetcode.cn/problems/longest-increasing-subsequence-ii/solution/fen-zhi-by-heltion-h31y/
// 每个前缀的 LIS
// LC1964 https://leetcode-cn.com/problems/find-the-longest-valid-obstacle-course-at-each-position/
lisAll := func(a []int) []int {
n := len(a)
lis := make([]int, n)
g := []int{}
for i, v := range a {
p := sort.SearchInts(g, v) // 改成 v+1 为非严格递增(即 upper_bound)
if p < len(g) {
g[p] = v
} else {
g = append(g, v)
}
lis[i] = p + 1
}
return lis
}
// LIS 方案数 O(nlogn)
// 原理见下面这题官方题解的方法二
// LC673 https://leetcode-cn.com/problems/number-of-longest-increasing-subsequence/
cntLis := func(a []int) int {
g := [][]int{} // 保留所有历史信息
cnt := [][]int{} // 个数前缀和
for _, v := range a {
p := sort.Search(len(g), func(i int) bool { return g[i][len(g[i])-1] >= v })
c := 1
if p > 0 {
// 根据 g[p-1] 来计算 cnt
i := sort.Search(len(g[p-1]), func(i int) bool { return g[p-1][i] < v })
c = cnt[p-1][len(cnt[p-1])-1] - cnt[p-1][i]
}
if p == len(g) {
g = append(g, []int{v})
cnt = append(cnt, []int{0, c})
} else {
g[p] = append(g[p], v)
cnt[p] = append(cnt[p], cnt[p][len(cnt[p])-1]+c)
}
}
c := cnt[len(cnt)-1]
return c[len(c)-1]
}
// LIS 相关构造题
// https://codeforces.com/problemset/problem/1304/D
// https://atcoder.jp/contests/arc091/tasks/arc091_c
// 最大上升子序列和
// 按值从小到大排序,值相同的下标从大到小排序
// 然后用树状数组或线段树:单点更新,维护前缀最大值
// https://www.acwing.com/problem/content/3665/
// 最长公共上升子序列 (LCIS)
// https://www.acwing.com/problem/content/274/
// https://codeforces.com/problemset/problem/10/D
lcis := func(a, b []int) int {
n, m := len(a), len(b)
dp := make([][]int, n+1)
for i := range dp {
dp[i] = make([]int, m)
}
for i, v := range a {
mx := 0
for j, w := range b {
if v == w {
dp[i+1][j] = mx + 1
} else {
dp[i+1][j] = dp[i][j]
}
if w < v {
mx = max(mx, dp[i][j])
}
}
}
ans := 0
for _, v := range dp[n] {
ans = max(ans, v)
}
return ans
}
// LCIS 打印方案
lcisPath := func(a, b []int) (ans int, lcis []int) {
n, m := len(a), len(b)
dp := make([][]int, n+1)
fa := make([][]int, n+1)
for i := range dp {
dp[i] = make([]int, m)
fa[i] = make([]int, m)
}
for i, v := range a {
mx, k := 0, -1
for j, w := range b {
if v == w {
dp[i+1][j] = mx + 1
fa[i+1][j] = k // k < j
} else {
dp[i+1][j] = dp[i][j]
fa[i+1][j] = j
}
if w < v && dp[i][j] > mx {
mx, k = dp[i][j], j
}
}
}
ansJ := 0
for j, dv := range dp[n] {
if dv > dp[n][ansJ] {
ansJ = j
}
}
ans = dp[n][ansJ]
var getLCIS func(i, j int)
getLCIS = func(i, j int) {
if i == 0 || j < 0 {
return
}
getLCIS(i-1, fa[i][j])
if fa[i][j] < j {
lcis = append(lcis, b[j])
}
}
getLCIS(n, ansJ)
return
}
// 长度为 m 的 LIS 个数
// 赤壁之战 https://www.acwing.com/problem/content/299/
// 定义 dp[i][j] 表示 a[:j+1] 的长度为 i 且以 a[j] 结尾的 LIS
// 则有 dp[i][j] = ∑dp[i-1][k] (k<j && a[k]<a[j])
// 注意到当 j 增加 1 时,只多了 k=j 这一个新决策,这样可以用树状数组来维护
// 复杂度 O(mnlogn)
countLIS := func(a []int, m int) int {
// 将 a 离散化成从 2 开始的序列
b := append([]int(nil), a...)
sort.Ints(b)
for i, v := range a {
a[i] = sort.SearchInts(b, v) + 2
}
n := len(a)
const mod int = 1e9 + 7
tree := make([]int, n+2)
add := func(i, val int) {
for ; i < n+2; i += i & -i {
tree[i] = (tree[i] + val) % mod
}
}
sum := func(i int) (res int) {
for ; i > 0; i &= i - 1 {
res = (res + tree[i]) % mod
}
return
}
dp := make([][]int, m+1)
for i := range dp {
dp[i] = make([]int, n)
}
for i := 1; i <= m; i++ {
tree = make([]int, n+2)
if i == 1 {
add(1, 1)
}
for j, v := range a {
dp[i][j] = sum(v - 1)
add(v, dp[i-1][j])
}
}
ans := 0
for _, v := range dp[m] {
ans = (ans + v) % mod
}
return ans
}
// 本质不同非空子序列个数
// 详细讲解见 https://leetcode.cn/problems/distinct-subsequences-ii/solution/xi-fen-wen-ti-fu-za-du-you-hua-pythonjav-1ihu/
// 模板题 LC940 https://leetcode-cn.com/problems/distinct-subsequences-ii/
// 倒序遍历即可 LC1987 https://leetcode-cn.com/problems/number-of-unique-good-subsequences/
// 需要一点构造能力 https://codeforces.com/problemset/problem/645/E
distinctSubsequence := func(s string) int {
const mod int = 1e9 + 7
f := [26]int{}
sumF := 0
for _, b := range s {
b -= 'a'
tmp := (sumF + mod - f[b]) % mod
f[b] = (sumF + 1) % mod
sumF = (tmp + f[b]) % mod
}
// 把空的也算上
//sumF = (sumF + 1) % mod
return sumF
}
// 扩展:长度为 k 的本质不同子序列个数
// 返回一个数组 f,f[k] 表示长度为 k 的本质不同子序列个数
// https://codeforces.com/problemset/problem/1183/H
// https://ac.nowcoder.com/acm/contest/4853/C 题解 https://ac.nowcoder.com/discuss/394080
//distinctSubsequenceWithFixedLength := func(s string) []int {
// const mod int = 1e9 + 7
//
// panic("impl me")
// //f := [26]int{}
// //
// //return sumF
//}
// 滚动数组写法
distinctSubsequence = func(s string) int {
const mod int = 1e9 + 7
last := make([]int, 26)
dp := 1
for _, v := range s {
v -= 'a'
res := dp - last[v]
if res < 0 {
res += mod
}
dp = (dp + res) % mod
last[v] = (last[v] + res) % mod
}
return (dp + mod - 1) % mod // 去掉空序列
}
// O(n^2) 计算 LCP —— 如果你不想用后缀数组的话
// LC1977 https://leetcode.cn/problems/number-of-ways-to-separate-numbers/description/
lcp := func(s string) {
n := len(s)
lcp := make([][]int, n+1)
for i := range lcp {
lcp[i] = make([]int, n+1)
}
for i := n - 1; i >= 0; i-- {
for j := n - 1; j >= 0; j-- {
if s[i] == s[j] {
lcp[i][j] = lcp[i+1][j+1] + 1
}
}
}
// 返回 s[l1:l2] <= s[l2:r2]
lessEq := func(l1, l2, r2 int) bool {
l := lcp[l1][l2]
return l >= r2-l2 || s[l1+l] < s[l2+l]
}
_ = lessEq
}
// 回文串:中心扩展法
// 原理见 https://leetcode.cn/problems/palindromic-substrings/solutions/379987/hui-wen-zi-chuan-by-leetcode-solution/
// LC647 https://leetcode.cn/problems/palindromic-substrings/
// LC2472 https://leetcode.cn/problems/maximum-number-of-non-overlapping-palindrome-substrings/
palindromeO1Space := func(s string) {
n := len(s)
for i := 0; i < 2*n-1; i++ { // i 为偶数表示奇回文串,i 为奇数表示偶回文串
l, r := i/2, i/2+i%2
// 从 s[i/2..i/2(+1)] 开始扩展
// do init ...
for l >= 0 && r < n && s[l] == s[r] {
// do s[l..r] ...
l--
r++
}