题目
https://www.lanqiao.cn/problems/240/learning/
方法一:暴力
1 | from itertools import permutations |
方法二:DP
思路:
参考一下这篇文章,主要是它图,然后用我的理解重新说一下。
首先是状态的定义。dp[i][j]
表示取1~i
的数组成的j
单调序列的数量。j
单调序列,也就是说这个序列中有j
个单调的区间,j-1
个折点,
现在考虑将i
插入到1~i-1
的排列中折点的数量变化情况。
假设现在这个序列是j
单调序列,最后结论是:
- 对于第1个区间,
- 如果是单调增,在最前面插入,折点数加1;
- 如果是单调减,在最前面插入,折点数不变,在第1个和第2个数之间插入,折点数加1;
- 对于最后1个区间,
- 如果是单调增,在最后面插入,折点数不变,在最后1个和倒数第2个之间插入,折点数加1;
- 如果是单调减,在最后面插入,折点数加1;
- 在波峰这个点的两边插入,折点数不变;
- 在其他位置插入,折点数加2。
发现折点最多增加2个。i
插入的位置按折点变化情况分类:
折点数不变
- 波峰这个点的两边
- 第一个区间如果单调减的最前面
- 最后一个区间如果单调增的最后面
其实,可以更加总结成:单调增区间的最后面或单调减区间的最前面。这里的区间的两端是波峰或波谷或整个序列的两端。这样的话,每个区间都能有一个位置让
i
插入后使得折点数不变,数量为区间个数j
;折点数加1
- 第1个区间如果单调增,插在最前面,如果单调减,插在第1个和第2个数之间
- 最后1个区间如果单调增,插在最后两个数之间,如果单调减,插在最后面
也就是说,不管单调的方向,整个序列中总有2个位置使得折点数加1;
折点数加2
其余位置。因为
i-1
的排列一共有i
个位置可以插入,前面用了`j+2
提醒的是:
- 要插入的
i
是比现有序列的所有数都大的。 i-1
个数一共有i
个可以插入的地方。j
单调序列,也就是说这个序列中有j
个单调的区间,j-1
个折点,
所以,dp[i][j]
是将i
插入到i-1
的排列中后折点数为j-1
,所以dp[i]][j]
是由dp[i-1][j]
、dp[i-1][j-1]
和dp[i-1][j-2]
转移过来:
dp[i-1][j]
,将i
插入i-1
的排列后折点数不变,数量有j
个;dp[i-1][j-1]
,将i
插入i-1
的排列后折点数加1,数量有2
个;dp[i-1][j-2]
,将i
插入i-1
的排列后折点数加2,这里的区间个数为j-2
,一共有i
个位置各异插入,去掉不变和加1两种情况的j-2
和2
个,所以有i-j
个。
所以状态转移方程为:dp[i][j]=dp[i-1][j]*j+dp[i-1][j-1]*2+dp[i-1][j-2]*(i-j)
。
初始状态:dp[2][1] = 2
,也就是两个数的两种排列情况。很多地方有dp[1][1] = 1
,我觉得无所谓。
代码
1 | n, k = map(int, input().split()) |
一点坑:
因为j
循环里和上一行j
之后的没有关系,所以我将dp
设置成了(n+1)*(k+1)
的大小,这个时候就必须要判断一下j-2
是否大于0,不然回事负数仍然会有值。