forked from EndlessCheng/codeforces-go
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathdp.go
4056 lines (3806 loc) · 161 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"
"slices"
"sort"
"strconv"
)
/* 动态规划(Dynamic Programming,DP)
题单:入门/背包/状态机/划分/区间/状压/数位/数据结构优化/树形/博弈/概率期望
https://leetcode.cn/circle/discuss/tXLS3i/
入门视频
https://www.bilibili.com/video/BV1Xj411K7oF/
如何用记忆化搜索打印具体方案
https://leetcode.cn/problems/shortest-common-supersequence/solution/cong-di-gui-dao-di-tui-jiao-ni-yi-bu-bu-auy8z/
① 前缀/后缀之间的转移,例如从 f[i-1] 转移到 f[i],或者从 f[j] 转移到 f[i]
LC70 爬楼梯 https://leetcode.cn/problems/climbing-stairs/
- LC509 https://leetcode.cn/problems/fibonacci-number/
- LC1137 https://leetcode.cn/problems/n-th-tribonacci-number/ 1143
- 变形:有障碍物 https://atcoder.jp/contests/abc129/tasks/abc129_c
- 变形:有花费 LC746 https://leetcode.cn/problems/min-cost-climbing-stairs/
- LC2466 https://leetcode.cn/problems/count-ways-to-build-good-strings/ 1694
- LC2533 https://leetcode.cn/problems/number-of-good-binary-strings/
- LC377 https://leetcode.cn/problems/combination-sum-iv/ 每次可以往上爬 nums[i] 步
LC198 打家劫舍 https://leetcode.cn/problems/house-robber/
- LC740 https://leetcode.cn/problems/delete-and-earn/
- 变形:恰好选 floor(n/2) 个 https://atcoder.jp/contests/abc162/tasks/abc162_f
- 变形:矩阵打家劫舍 https://codeforces.com/problemset/problem/1195/C
LC213 环形打家劫舍 https://leetcode.cn/problems/house-robber-ii/
- 相似题目 https://atcoder.jp/contests/abc251/tasks/abc251_e
LC276 https://leetcode.cn/problems/paint-fence/
LC368 https://leetcode.cn/problems/largest-divisible-subset/
LC2369 https://leetcode.cn/problems/check-if-there-is-a-valid-partition-for-the-array/ 1780
- 变形:改成环形数组要怎么做
- 相似题目 https://codeforces.com/problemset/problem/1624/E 2000
LC983 https://leetcode.cn/problems/minimum-cost-for-tickets/ 1786
LC1416 https://leetcode.cn/problems/restore-the-array/ 1920
LC2944 https://leetcode.cn/problems/minimum-number-of-coins-for-fruits/
LC2297 https://leetcode.cn/problems/jump-game-viii/
LCR165 https://leetcode.cn/problems/ba-shu-zi-fan-yi-cheng-zi-fu-chuan-lcof/
https://codeforces.com/contest/1547/problem/E 1500
https://codeforces.com/problemset/problem/1875/D 1600
另见「最长递增子序列」
② 双序列问题,一般定义 f[i][j] 表示对子问题 (s1[:i],s2[:j]) 的求解结果
见下面的「最长公共子序列」,包含大量扩展题目
③ 划分型 DP:将数组分成恰好(或至多)k 个连续子数组,求解与这些子数组有关的最优值
一般定义 f[i][j] 表示将 a[:j] 分成 i 个连续子数组所得到的最优解
此时可以枚举最后一个子数组的左端点 L,从 f[i-1][L] 转移到 f[i][j],并考虑 a[L:j] 对最优解的影响
- [410. 分割数组的最大值](https://leetcode.cn/problems/split-array-largest-sum/) 做法不止一种
- [813. 最大平均值和的分组](https://leetcode.cn/problems/largest-sum-of-averages/) 1937
- [1278. 分割回文串 III](https://leetcode.cn/problems/palindrome-partitioning-iii/) 1979
- [1335. 工作计划的最低难度](https://leetcode.cn/problems/minimum-difficulty-of-a-job-schedule/) 2035
- [2478. 完美分割的方案数](https://leetcode.cn/problems/number-of-beautiful-partitions/) 2344
- [3077. K 个不相交子数组的最大能量值](https://leetcode.cn/problems/maximum-strength-of-k-disjoint-subarrays/) 2557
- [2911. 得到 K 个半回文串的最少修改次数](https://leetcode.cn/problems/minimum-changes-to-make-k-semi-palindromes/) 2608
https://www.luogu.com.cn/problem/P2679 2023.11.30 茶
https://codeforces.com/problemset/problem/1969/C 1700
https://codeforces.com/problemset/problem/797/F 2600
④ 划分型 DP:最小化/最大化分割出的子数组个数等
- [132. 分割回文串 II](https://leetcode.cn/problems/palindrome-partitioning-ii/)
至多 k 个 https://codeforces.com/problemset/problem/137/D
- [2707. 字符串中的额外字符](https://leetcode.cn/problems/extra-characters-in-a-string/) 1736
- [2767. 将字符串分割为最少的美丽子字符串](https://leetcode.cn/problems/partition-string-into-minimum-beautiful-substrings/) 1865
- [1105. 填充书架](https://leetcode.cn/problems/filling-bookcase-shelves/) 2014
- [2547. 拆分数组的最小代价](https://leetcode.cn/problems/minimum-cost-to-split-an-array/) 2020
- [2463. 最小移动总距离](https://leetcode.cn/problems/minimum-total-distance-traveled/) 2454
- [2977. 转换字符串的最小成本 II](https://leetcode.cn/problems/minimum-cost-to-convert-string-ii/) 2696
- [2052. 将句子分隔成行的最低成本](https://leetcode.cn/problems/minimum-cost-to-separate-sentence-into-rows/)(会员题)
https://codeforces.com/problemset/problem/1005/D 1500
https://codeforces.com/problemset/problem/1714/D 1600 允许重叠 输出方案
https://www.luogu.com.cn/problem/P1874
⑤ 多维 / 额外状态
LC1223 https://leetcode.cn/problems/dice-roll-simulation/ 2008
LC2919 https://leetcode.cn/problems/minimum-increment-operations-to-make-array-beautiful/ 2031 状态设计的好题
LC2209 https://leetcode.cn/problems/minimum-white-tiles-after-covering-with-carpets/ 2106
LC956 https://leetcode.cn/problems/tallest-billboard/ 2381
LC920 https://leetcode.cn/problems/number-of-music-playlists/ 2400
LC1531 看起来是区间 DP,仔细分析后是线性 DP https://leetcode.cn/problems/string-compression-ii/ 2576
LC2464 https://leetcode.cn/problems/minimum-subarrays-in-a-valid-split/ 枚举选哪个
https://codeforces.com/contest/404/problem/D 1900
https://codeforces.com/problemset/problem/1920/E 2000
https://codeforces.com/problemset/problem/6/D 2600
从 X 操作到 Y(部分题目也可以用 BFS)
+1 -1 /2 [397. 整数替换](https://leetcode.cn/problems/integer-replacement/)
-1 +1 /5 /11 [2998. 使 X 和 Y 相等的最少操作次数](https://leetcode.cn/problems/minimum-number-of-operations-to-make-x-and-y-equal/) 1795
+a[i] -a[i] ^a[i] [2059. 转化数字的最小运算数](https://leetcode.cn/problems/minimum-operations-to-convert-number/) 1850
-1 *2 [991. 坏了的计算器](https://leetcode.cn/problems/broken-calculator/) 1909
/2 /3 [1553. 吃掉 N 个橘子的最少天数](https://leetcode.cn/problems/minimum-number-of-days-to-eat-n-oranges/) 2048
-1 /k https://codeforces.com/problemset/problem/940/B 1400
[LCP 09. 最小跳跃次数](https://leetcode.cn/problems/zui-xiao-tiao-yue-ci-shu/)
[LCP 20. 快速公交](https://leetcode.cn/problems/meChtZ/)
*5 /6 https://ac.nowcoder.com/acm/contest/71512/D
预处理
LC2638 https://leetcode.cn/problems/count-the-number-of-k-free-subsets/
todo 题单 https://www.luogu.com.cn/training/83815#problems
跳台阶+禁入点 https://atcoder.jp/contests/abc289/tasks/abc289_d
入门计数 DP https://atcoder.jp/contests/abc248/tasks/abc248_c
https://atcoder.jp/contests/abc281/tasks/abc281_d
选或不选 [1800·hot10] https://codeforces.com/contest/1525/problem/D
https://codeforces.com/problemset/problem/176/B
https://codeforces.com/problemset/problem/1324/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
https://codeforces.com/problemset/problem/1845/E
LC2143 https://leetcode.cn/problems/choose-numbers-from-two-arrays-in-range/
不相交区间 DP
- [2830. 销售利润最大化](https://leetcode.cn/problems/maximize-the-profit-as-the-salesman/) 1851
- [2008. 出租车的最大盈利](https://leetcode.cn/problems/maximum-earnings-from-taxi/) 1872
- [1235. 规划兼职工作](https://leetcode.cn/problems/maximum-profit-in-job-scheduling/) 2023
- [1751. 最多可以参加的会议数目 II](https://leetcode.cn/problems/maximum-number-of-events-that-can-be-attended-ii/) 2041
https://codeforces.com/problemset/problem/1801/C
LC2054 贪心 https://leetcode.cn/problems/two-best-non-overlapping-events/
排列型/插入型
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
网格路径问题 网格图 DP
#### 练习 1
- [62. 不同路径](https://leetcode.cn/problems/unique-paths/)
- [63. 不同路径 II](https://leetcode.cn/problems/unique-paths-ii/)
- [64. 最小路径和](https://leetcode.cn/problems/minimum-path-sum/)
- 变形:连续性 & 上下界思想 https://codeforces.com/contest/1695/problem/C
- https://atcoder.jp/contests/arc137/tasks/arc137_b 也用到了这个思想
- [120. 三角形最小路径和](https://leetcode.cn/problems/triangle/)
- https://www.luogu.com.cn/problem/P1216
- 额外状态 https://www.luogu.com.cn/problem/P1544
- [2684. 矩阵中移动的最大次数](https://leetcode.cn/problems/maximum-number-of-moves-in-a-grid/) 1626
- [1301. 最大得分的路径数目](https://leetcode.cn/problems/number-of-paths-with-max-score/) 1853
#### 练习 2
- [329. 矩阵中的最长递增路径](https://leetcode.cn/problems/longest-increasing-path-in-a-matrix/)
- [2328. 网格图中递增路径的数目](https://leetcode.cn/problems/number-of-increasing-paths-in-a-grid/) 2001
#### 练习 3
- [1289. 下降路径最小和 II](https://leetcode.cn/problems/minimum-falling-path-sum-ii/) 1697
- [2435. 矩阵中和能被 K 整除的路径](https://leetcode.cn/problems/paths-in-matrix-whose-sum-is-divisible-by-k/) 1952
- [741. 摘樱桃](https://leetcode.cn/problems/cherry-pickup/)
- https://codeforces.com/problemset/problem/213/C 2000
- https://www.luogu.com.cn/problem/P1004 [NOIP2000 提高组] 方格取数
- https://atcoder.jp/contests/abc320/tasks/abc320_f 2042
- [1463. 摘樱桃 II](https://leetcode.cn/problems/cherry-pickup-ii/) 1957
- 回文串 https://codeforces.com/problemset/problem/570/E
每行至多选三个 https://atcoder.jp/contests/abc175/tasks/abc175_e
图 DP
https://codeforces.com/problemset/problem/346/D 2600
思考过程:
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.(进阶)状态不好确定时,尝试转化问题模型、逆序思考、增加维度等等。(试试下面的题目)
题目已经分类整理好:试试搜索「最大子段和」等。
常规题目
预处理 https://codeforces.com/contest/1932/problem/F
记忆化搜索
- [1387. 将整数按权重排序](https://leetcode.cn/problems/sort-integers-by-the-power-value/) 1507
如何设计状态
https://codeforces.com/problemset/problem/1461/B 1400
https://codeforces.com/problemset/problem/553/A 1500
https://codeforces.com/problemset/problem/1286/A 1800
https://codeforces.com/problemset/problem/14/E 1900
https://codeforces.com/problemset/problem/452/D 1900 题解 https://www.luogu.com.cn/blog/endlesscheng/solution-cf452d
https://codeforces.com/problemset/problem/687/C 1900
https://codeforces.com/problemset/problem/1012/C 1900
https://codeforces.com/problemset/problem/360/B 2000
https://codeforces.com/problemset/problem/461/B 2000
todo https://codeforces.com/problemset/problem/571/B 2000
https://codeforces.com/problemset/problem/1408/D 2000
https://codeforces.com/problemset/problem/1783/D 2000 推公式
https://codeforces.com/problemset/problem/1025/D 2100
https://codeforces.com/problemset/problem/1027/E 2100
https://codeforces.com/problemset/problem/1579/G 2200
todo https://codeforces.com/problemset/problem/441/E 2400 考虑 x+i 的尾零个数
https://codeforces.com/contest/1927/problem/G
https://atcoder.jp/contests/arc115/tasks/arc115_e 容斥
- https://codeforces.com/contest/1591/problem/F
todo https://codeforces.com/problemset/problem/744/C 2400
https://codeforces.com/problemset/problem/840/C 2500
https://atcoder.jp/contests/abc237/tasks/abc237_f
https://atcoder.jp/contests/abc232/tasks/abc232_e
混合逆序对 https://atcoder.jp/contests/arc097/tasks/arc097_c
寻找子问题 https://atcoder.jp/contests/arc116/tasks/arc116_d
todo https://atcoder.jp/contests/abc200/tasks/abc200_e
SEERC05,紫书例题 9-3,UVa 1347 https://onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&category=446&page=show_problem&problem=4093
LC2919 https://leetcode.cn/problems/minimum-increment-operations-to-make-array-beautiful/ 2031
LC3193 https://leetcode.cn/problems/count-the-number-of-inversions/ 利用排列的性质
LC956 https://leetcode.cn/problems/tallest-billboard/ 2381
LC1388 https://leetcode.cn/problems/pizza-with-3n-slices/ 2410
LC903 DI 序列的有效排列 https://leetcode.cn/problems/valid-permutations-for-di-sequence/ 2433
LC2638 https://leetcode.cn/problems/count-the-number-of-k-free-subsets/
https://www.luogu.com.cn/problem/P9688?contestId=133572
《挑战》pp.62-64 多重部分和问题
如何消除后效性(通过巧妙地设计状态/发现性质)
LC2896 执行操作使两个字符串相等 https://leetcode.cn/problems/apply-operations-to-make-two-strings-equal/
LC312 戳气球 https://leetcode.cn/problems/burst-balloons/
LC546 消消乐 https://leetcode.cn/problems/remove-boxes/ https://leetcode.com/contest/leetcode-weekly-contest-25
UVa1625 合并序列 最小化所有元素第一次和最后一次出现的位置差的和 https://onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&category=825&page=show_problem&problem=4500
涉及到相邻状态先后关系(喂兔子)https://codeforces.com/problemset/problem/358/D
状态优化
LC935 减少状态个数 https://leetcode.cn/problems/knight-dialer/
妙!https://atcoder.jp/contests/abc243/tasks/abc243_g
输出方案
LC1092 https://leetcode.cn/problems/shortest-common-supersequence/
- 题解:https://leetcode.cn/problems/shortest-common-supersequence/solution/cong-di-gui-dao-di-tui-jiao-ni-yi-bu-bu-auy8z/
LC2212 https://leetcode.cn/problems/maximum-points-in-an-archery-competition/
值域 DP
常见于递增子序列相关的题目
LC3041 https://leetcode.cn/problems/maximize-consecutive-elements-in-an-array-after-modification/
https://codeforces.com/problemset/problem/1582/F1
决策单调性
https://codeforces.com/problemset/problem/229/D
增量法
见 common.go
思维转换
谁来当 DP 对象 LC1434 https://leetcode.cn/problems/number-of-ways-to-wear-different-hats-to-each-other/
扔蛋问题 LC887 https://leetcode.cn/problems/super-egg-drop/ https://www.bilibili.com/video/BV1KE41137PK
LC920* https://leetcode.cn/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
贪心+DP
https://leetcode.cn/problems/minimum-time-to-make-array-sum-at-most-x/
https://codeforces.com/problemset/problem/1799/F 2700
NOTE: 无后效性是指当前的决策只与过去的结果有关,而与过去的决策无关
NOTE: 若状态转移不构成 DAG,请尝试建图+BFS,见:
https://ac.nowcoder.com/acm/contest/6218/B
https://codeforces.com/problemset/problem/283/B 活用 012 染色
https://codeforces.com/problemset/problem/1272/F
- 也可以在记忆化搜索的过程中,提前设置 memo 的值,来避免陷入死循环 https://codeforces.com/problemset/submission/1272/208121980
NOTE: 递归套递归打印方案 https://leetcode.cn/problems/shortest-common-supersequence/solutions/2194615/cong-di-gui-dao-di-tui-jiao-ni-yi-bu-bu-auy8z/
NOTE: 若使用滚动数组,注意复用时可能要初始化
NOTE:(区间 DP)正向计算不易时,试着反向计算
TIPS: 若转移是若干相邻项之和,可以考虑 f(p) - f(p-1) 的值,用滑动窗口来维护区间和,从而优化转移
例题 LC837 https://leetcode.cn/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_comb.go 中的「一些组合问题」
入门计数 DP https://atcoder.jp/contests/abc248/tasks/abc248_c
入门计数 DP LC1079 https://leetcode.cn/problems/letter-tile-possibilities/
- 相似技巧 LC1681 https://leetcode.cn/problems/minimum-incompatibility/discussion/comments/2051770
https://codeforces.com/problemset/problem/414/B 1400
https://codeforces.com/problemset/problem/1794/D 1900
https://codeforces.com/problemset/problem/1767/C 2100 带约束的计数 DP
https://codeforces.com/problemset/problem/626/F 2400 转换
https://codeforces.com/problemset/problem/1237/F 2600
todo https://atcoder.jp/contests/abc234/tasks/abc234_f
多重组合
- 见「多重背包 - 求方案数 - 同余前缀和优化」
多重排列
- f[i][j] 表示前 i 类数字组成长为 j 的排列个数
- f[i][j] = ∑f[i-1][k]*C(j,k), 0<=k<=min(j,cnt[i])
- 边界 f[0][0] = 1
贪心优化 DP
https://codeforces.com/problemset/problem/864/E
双指针优化 DP
https://codeforces.com/problemset/problem/883/I
https://training.olinfo.it/#/task/preoii_yutaka/statement
我的视频讲解:
https://www.bilibili.com/video/BV1Xj411K7oF 从记忆化搜索到递推
https://www.bilibili.com/video/BV16Y411v7Y6 背包问题
https://www.bilibili.com/video/BV1TM4y1o7ug LCS
https://www.bilibili.com/video/BV1ub411Q7sB LIS
https://www.bilibili.com/video/BV1ho4y1W7QK 状态机 DP
https://www.bilibili.com/video/BV1Gs4y1E7EU 区间 DP
https://www.bilibili.com/video/BV17o4y187h1 树形 DP
https://www.bilibili.com/video/BV1vu4y1f7dn 树形 DP
https://www.bilibili.com/video/BV1oF411U7qL 树形 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 f[i分钟][j移动次数] = max(f[i-1][j], f[i-1][j-1]) + 当前分钟是否有苹果落在 j 次移动后的位置 最后答案为 max{f[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/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/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 _(abs func(int) int) {
// 由于数据范围的原因,采用 map 记忆化 dpMap
// LC1553 https://leetcode.cn/problems/minimum-number-of-days-to-eat-n-oranges/
// LC2998 https://leetcode.cn/problems/minimum-number-of-operations-to-make-x-and-y-equal/
// LC638 https://leetcode.cn/problems/shopping-offers/
// 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) (ans int) {
{
// 一维(多维见下)
memo := map[int]int{}
var f func(int) int
f = func(x int) (res int) {
//if x == 0 {
// return
//}
if v, ok := memo[x]; ok {
return v
}
defer func() { memo[x] = res }()
return
}
ans = f(n)
}
{
// 多维
type pair struct{ x, y int }
memo := 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 := memo[p]; ok {
return v
}
defer func() { memo[p] = res }()
return
}
ans = f(0, 0)
}
return
}
// 最大子段和 / 最大子数组和
// 有三种思路
// 1. Kadane 算法:定义状态 f[i] 表示以 a[i] 结尾的最大子段和,则有状态转移方程 f[i]=max(f[i−1],0)+a[i],答案为 max(f)
// 2. 前缀和:遍历 a 的同时维护前缀和的最小值,则遍历到 a[i] 时,当前最大子段和为 sum[i]-min(sum[j]), j<i
// - 我的题解:https://leetcode.cn/problems/maximum-subarray/solution/qian-zhui-he-zuo-fa-ben-zhi-shi-mai-mai-abu71/
// 3. 分治:通常用于一些带修改的题目,需要用到线段树 https://www.luogu.com.cn/problem/P4513 https://codeforces.com/contest/1843/problem/F2
//
// LC53 https://leetcode.cn/problems/maximum-subarray/
// https://www.luogu.com.cn/problem/P1115
// LC2606 https://leetcode.cn/problems/find-the-substring-with-maximum-cost/
//
// 二维版本
// https://leetcode.cn/problems/max-submatrix-lcci/
// https://www.luogu.com.cn/problem/P1719
//
// 《算法导论》练习 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
// 一道题考察上面两个限制 https://codeforces.com/problemset/problem/1796/D
// - O(n) 做法 https://codeforces.com/contest/1796/submission/263191415
// 子段和有上限的最大子段和:转换为前缀和之差 sum[i]-sum[j]<=K,在平衡树上二分 sum[j]
// - LC363 https://leetcode.cn/problems/max-sum-of-rectangle-no-larger-than-k/
// 最大两段子段和:求每个位置上的前缀最大子段和和后缀最大子段和 https://www.luogu.com.cn/problem/P2642
// - 等价题目:允许翻转一段子区间的最大子段和
// 最大三段子段和 LC689 https://leetcode.cn/problems/maximum-sum-of-3-non-overlapping-subarrays/ *长度固定为 k
// 删除至多一个元素后的最大子段和 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/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
// 变形题:
// LC2321 https://leetcode.cn/problems/maximum-score-of-spliced-array/
// LC1749 https://leetcode.cn/problems/maximum-absolute-sum-of-any-subarray/
// - 另一种做法是计算前缀和的最大值与最小值的差
// LC1191 重复 k 次 https://leetcode.cn/problems/k-concatenation-maximum-sum/
// https://codeforces.com/problemset/problem/1285/B 1300
// https://codeforces.com/problemset/problem/788/A 1600
// https://codeforces.com/problemset/problem/1373/D 1600
// https://codeforces.com/problemset/problem/33/C 1800
// https://codeforces.com/problemset/problem/1845/D 1800 本质是去掉一个最小的子段
// https://codeforces.com/problemset/problem/1155/D 1900
// https://codeforces.com/problemset/problem/1197/D 1900 思路 https://docs.qq.com/sheet/DWGFoRGVZRmxNaXFz 里面搜本题链接
// https://codeforces.com/problemset/problem/1082/E 2000 需要一些转换技巧
// https://atcoder.jp/contests/arc137/tasks/arc137_b
// https://codeforces.com/problemset/problem/75/D 2000 多个小数组合并
// - 这题做法需要用到上面说到的第二种思路
// 二维的情况(最大子阵和)可以枚举上下边界,转换成一维 O(n^3)
// 树上的情况 https://codeforces.com/contest/1843/problem/F2 2300
maxSubarraySum := func(a []int) int {
if len(a) == 0 { // 根据题意返回
return 0
}
maxS, sum := math.MinInt, 0
for _, v := range a {
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
}
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
}
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)
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
}
// 最大子序列交替和(买卖股票)
// 有两种思路:
// 1. 动态规划,具体见我的题解 https://leetcode.cn/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/problems/maximum-alternating-subsequence-sum/
// 变形 LC1526 https://leetcode.cn/problems/minimum-number-of-increments-on-subarrays-to-form-a-target-array/
// LC122 https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-ii/
// 扩展:O(1) 回答交换其中两个元素后的最大子序列交替和 https://codeforces.com/problemset/problem/1420/C2
maxAlternatingSumDP := func(a []int) int {
f := [2]int{0, -1e9}
for _, v := range a {
f = [2]int{max(f[0], f[1]-v), max(f[1], f[0]+v)}
}
return f[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])
}
return
}
// 修改序列为非降或非增的最小修改次数
// - 单次修改可以把某个数 +1 或 -1
// https://writings.sh/post/slope-trick-mono-sequence
//
// 通过一个例子来解释这个基于堆的算法: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
// ...
// LC2263 https://leetcode.cn/problems/make-array-non-decreasing-or-non-increasing/
// https://codeforces.com/problemset/problem/13/C 2200
// https://codeforces.com/problemset/problem/713/C 2300 严格单调递增 https://codeforces.com/blog/entry/47094?#comment-315161
// 这道题做了一个 a[i]-=i 的操作(i 从 1 开始),把严格单调递增变成了非降的情况,从而可以应用该算法
// 这一技巧的原理是,对于整数来说,单调递增的最小情况是 y=x+C,减去这一函数,就得到了非降序列的最小情况 y=C
// https://www.luogu.com.cn/problem/P4597
// https://www.luogu.com.cn/problem/P2893
// http://poj.org/problem?id=3666
slopeTrick := func(a []int) int {
h := hp{} // 大根堆
ans := 0
for _, v := range a {
h.push(v)
if d := h.IntSlice[0] - v; d > 0 {
ans += d
h.IntSlice[0] = v
heap.Fix(&h, 0)
}
}
return ans
}
// 返回一个矩阵 f,其中 f[i+1][j+1] 表示包含【左上角为 (0,0) 右下角为 (i,j) 的子矩形】中的所有 1 的最小矩形面积
// https://leetcode.cn/problems/find-the-minimum-area-to-cover-all-ones-ii/
// 类似题目【分三块】:
// https://www.luogu.com.cn/problem/P3625
// - https://atcoder.jp/contests/abc347/tasks/abc347_f
// https://atcoder.jp/contests/abc062/tasks/arc074_a
minimumArea := func(a [][]int) [][]int {
n, m := len(a), len(a[0])
// f[i+1][j+1] 表示包含【左上角为 (0,0) 右下角为 (i,j) 的子矩形】中的所有 1 的最小矩形面积
f := make([][]int, n+1)
for i := range f {
f[i] = make([]int, m+1)
}
type data struct{ top, left, right int }
border := make([]data, m)
for j := range border {
border[j].top = -1 // 无
}
for i, row := range a {
left, right := -1, 0
for j, x := range row {
if x > 0 {
if left < 0 {
left = j
}
right = j
}
preB := border[j]
if left < 0 { // 这一排目前全是 0
f[i+1][j+1] = f[i][j+1] // 等于上面的结果
} else if preB.top < 0 { // 这一排有 1,上面全是 0
f[i+1][j+1] = right - left + 1
border[j] = data{i, left, right}
} else { // 这一排有 1,上面也有 1
l, r := min(preB.left, left), max(preB.right, right)
f[i+1][j+1] = (r - l + 1) * (i - preB.top + 1)
border[j] = data{preB.top, l, r}
}
}
}
return f
}
// 最长公共子序列 (LCS)
// 视频讲解:https://www.bilibili.com/video/BV1TM4y1o7ug/
// 更快的做法(位运算)见 SPOJ LCS0 https://www.luogu.com.cn/problem/SP12076
//
// LC1143 模板题 https://leetcode.cn/problems/longest-common-subsequence/
// LC72 编辑距离 https://leetcode.cn/problems/edit-distance/
// - 热身 LC161 https://leetcode.cn/problems/one-edit-distance/
// - 进阶:编辑距离 + 可以交换相邻字母 https://codeforces.com/problemset/problem/67/C 2600
// LC97 https://leetcode.cn/problems/interleaving-string/
// LC115 https://leetcode.cn/problems/distinct-subsequences/
// LC583 https://leetcode.cn/problems/delete-operation-for-two-strings/
// LC712 https://leetcode.cn/problems/minimum-ascii-delete-sum-for-two-strings/
// LC1035 https://leetcode.cn/problems/uncrossed-lines/ 1806
// LC1458 https://leetcode.cn/problems/max-dot-product-of-two-subsequences/ 1824
// LC1092 最短公共超序列 (SCS) https://leetcode.cn/problems/shortest-common-supersequence/ 1977
// LC1639 https://leetcode.cn/problems/number-of-ways-to-form-a-target-string-given-a-dictionary/ 2082
// LC1713 https://leetcode.cn/problems/minimum-operations-to-make-a-subsequence/ 2351 若其中一个序列无重复元素,可以转换成 LIS
// - https://www.luogu.com.cn/problem/P1439
// https://codeforces.com/problemset/problem/163/A 1700 其中一个改为子串
// https://codeforces.com/problemset/problem/1446/B 1800
// https://codeforces.com/problemset/problem/463/D 1900 多个排列的 LCS
// - https://www.luogu.com.cn/problem/P2364 三个字符串的 LCS + 输出方案
// https://codeforces.com/problemset/problem/1114/D 1900 转换【巧妙】
// https://codeforces.com/problemset/problem/346/B 2000 与 KMP 结合
// - follow up: 要求某个子串 sub 一定在 LCS 中
// https://codeforces.com/problemset/problem/1584/F 2600 多串 + 每种字母至多出现两次
// - f[c][mask],其中 mask 记录字母 c 在每个字符串中的出现位置,0 表示左边那个,1 表示右边那个
// https://atcoder.jp/contests/abc185/tasks/abc185_e 权值
// https://atcoder.jp/contests/abc130/tasks/abc130_e 相同子序列个数
// 2020 多校第二场 https://acm.hdu.edu.cn/showproblem.php?pid=6774
lcs := func(s, t []byte) int {
// f[i][j] = LCS(s[:i], t[:j])
n, m := len(s), len(t)
f := make([][]int, n+1)
for i := range f {
f[i] = make([]int, m+1)
}
for i, v := range s {
for j, w := range t {
if v == w {
f[i+1][j+1] = f[i][j] + 1
} else {
f[i+1][j+1] = max(f[i][j+1], f[i+1][j])
}
}
}
{
// EXTRA: 某些 dp 非单调性的题目需要计算全局最值
allMax := 0
for _, row := range f {
for _, v := range row {
allMax = max(allMax, v)
}
}
}
return f[n][m]
}
lcsPath := func(s, t []byte) []byte {
n, m := len(s), len(t)
f := make([][]int, n+1)
for i := range f {
f[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 {
f[i+1][j+1] = f[i][j] + 1
fa[i+1][j+1] = 1
} else {
if f[i][j+1] > f[i+1][j] {
f[i+1][j+1] = f[i][j+1]
fa[i+1][j+1] = 2
} else {
f[i+1][j+1] = f[i+1][j]
fa[i+1][j+1] = 3
}
}
}
}
lcs := make([]byte, 0, f[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
}
// LCS 个数
// https://www.luogu.com.cn/problem/P2516
lcsCount := func(s, t []byte) int {
const mod = 1_000_000_007
n, m := len(s), len(t)
lcs := make([][]int, n+1)
count := make([][]int, n+1)
for i := 0; i <= n; i++ {
lcs[i] = make([]int, m+1)
count[i] = make([]int, m+1)
}
for i := 0; i <= n; i++ {
count[i][0] = 1
}
for j := 0; j <= m; j++ {
count[0][j] = 1
}
for i := 1; i <= n; i++ {
for j := 1; j <= m; j++ {
if s[i-1] == t[j-1] {
lcs[i][j] = lcs[i-1][j-1] + 1
count[i][j] = count[i-1][j-1] // 延续之前的子序列数
} else {
lcs[i][j] = max(lcs[i-1][j], lcs[i][j-1])
}
if lcs[i][j] == lcs[i-1][j] {
count[i][j] += count[i-1][j] // 加上不考虑 s2[j-1] 的子序列数
}
if lcs[i][j] == lcs[i][j-1] {
count[i][j] += count[i][j-1] // 加上不考虑 s1[i-1] 的子序列数
}
if s[i-1] != t[j-1] && lcs[i][j] == lcs[i-1][j-1] {
count[i][j] -= count[i-1][j-1] // 避免重复子序列的统计
}
count[i][j] %= mod
}
}
return (count[n][m] + mod) % mod
}
// 最长回文子序列 (LPS)
// 见下面的「区间 DP」
// 最长上升子序列 (LIS)
// 视频讲解:https://www.bilibili.com/video/BV1ub411Q7sB/
// 这种写法适用于一些定义比较复杂的变形题
// O(n^2) - 定义 f[i] 为以 a[i] 为末尾的 LIS 的长度
// 可以把此问题想象成一个「跳跃游戏」,任选一个初始位置向右跳跃,每次只能跳到比当前位置更高的位置,问最多能跳多少次(最后答案加一)
// 这样能更容易地看出转移的顺序,然后变成一个 DAG 上求最长路的问题
// 转换 http://acm.hdu.edu.cn/showproblem.php?pid=1950
// 变体 https://codeforces.com/problemset/problem/1350/B 1400
// todo 转换 https://codeforces.com/problemset/problem/1562/E
//【网络流 24 题】能取出多少个长为 len(LIS) 的不相交子序列 https://loj.ac/p/6005 https://www.luogu.com.cn/problem/P2766
lisSlow := func(a []int) int {
n := len(a)
f := make([]int, n)
for i, v := range a {
f[i] = 1
for j, w := range a[:i] {
if w < v { // 改成 <= 为非降
f[i] = max(f[i], f[j]+1)
}
}
}
return slices.Max(f)
}
// 最长上升子序列 (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/ 1933
// 建模 https://codeforces.com/problemset/problem/269/B 1700
// 经典转换(最多相交问题) https://codeforces.com/problemset/problem/67/D https://atcoder.jp/contests/arc126/tasks/arc126_b
// 最小划分数(导弹拦截)https://www.luogu.com.cn/problem/P1020
// - LC3231 https://leetcode.cn/problems/minimum-number-of-increasing-subsequence-to-be-removed/
// 转化成最小划分数+打印划分方案 https://codeforces.com/problemset/problem/1296/E2
// 合唱队形 https://www.luogu.com.cn/problem/P1091
// LC1671 合唱队形(至少有升有降)https://leetcode.cn/problems/minimum-number-of-removals-to-make-mountain-array/ 1913
// 二维 LIS LC354 https://leetcode.cn/problems/russian-doll-envelopes/
// 二维 LIS + 打印方案 https://codeforces.com/problemset/problem/4/D 1700
// 重复数组的 LIS https://codeforces.com/problemset/problem/582/B 1900 非降
// - todo https://codeforces.com/problemset/problem/261/D 2600 严格递增
// - todo https://codeforces.com/contest/261/submission/31860735
// 若其中一个序列无重复元素,LCS 可以转换成 LIS https://www.luogu.com.cn/problem/P1439
// - LC1713 https://leetcode.cn/problems/minimum-operations-to-make-a-subsequence/ 2351
// 在一维 LIS 的基础上,a[i] 可以从多个数中选一个,问 LIS 最长可以多长
// - 思路:将各个 a[i] 的可选项从大到小排序,然后拼接成一个序列,求 LIS 即可(关键:从大到小排序避免了在同一个可选项中选择多个元素)
// 插入后最小化 LIS https://codeforces.com/problemset/problem/1893/B 1700
// 图上的路径的 LIS https://codeforces.com/problemset/problem/960/F 2100
// LaIS 与单调栈结合 https://codeforces.com/problemset/problem/1468/A 2200
// 将所有 a[i] 分成三类:不在任何 LIS / 在至少一个 LIS / 在所有 LIS https://codeforces.com/problemset/problem/486/E 2200
// - 详细证明 https://www.luogu.com.cn/article/m4267u8v
// - 弱化版 https://atcoder.jp/contests/abc354/tasks/abc354_f
// - 删除元素+Dilworth https://ac.nowcoder.com/acm/contest/3782/C
// - 单点修改后计算 LIS https://codeforces.com/problemset/problem/650/D 2600
// 状态设计 LIS 计数 https://atcoder.jp/contests/abc237/tasks/abc237_f
// 逆向题:输入 LIS 返回字典序最小的排列 a https://atcoder.jp/contests/arc125/tasks/arc125_c
// 反向构造:构造一个 LIS 个数是 x 的数组
// - 这里把 x 定义成非空 LIS 的个数。把 x 二进制拆分成 2^m1 + 2^m2 + 2^m3 + ...
// - 例如 13 = 2^3 + 2^2 + 2^0,我们可以构造 91,91,92,92,93,93,81,82,82,83,83,71,72,73,
// - 看成三段,每一段的贡献就是前面拆分出的二进制数(这里只是举了个例子,每一段的 gap 可以调大一些以满足构造要求)
// bitset 优化 https://codeforces.com/contest/1826/problem/E
// 思想 https://codeforces.com/problemset/problem/1582/F1
lis := func(a []int) int {
g := []int{}
for _, v := range a {
j := sort.SearchInts(g, v) // 改成 v+1 为非严格递增(即 upper_bound)
if j < len(g) {
g[j] = 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/problems/find-the-longest-valid-obstacle-course-at-each-position/ 1933
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/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)
f := make([][]int, n+1)
for i := range f {
f[i] = make([]int, m)
}
for i, v := range a {
mx := 0
for j, w := range b {
if v == w {
f[i+1][j] = mx + 1
} else {
f[i+1][j] = f[i][j]