第十届蓝桥杯决赛B组:排列数

这题我们用动态规划做!

首先我们来找规律:

对于一个递增的数列,如123456,我们插入一个数。这个数大于数列中所有的数,这里插入7。如果不插在两端(1, 6)的数两侧,则增加了两个拐点,如1273456;插在(1, 6)的内测,有两种情况,如1723456和1234576;插在两端,有两种情况,7123456和1234567。

向一个数列的拐点两侧插入数字时,如果这个拐点比两边的数大,则拐点数不增加,如132变成1432或1342;如果这个拐点比两边的数小,则拐点数加2,如312变成3412或3142.

然后我们可以把插入数分为不增加拐点,增加1个拐点和增加两个拐点,共三种情况。

让dp[i][j]表示长度为i的列,拐点数为j时的所有排列的种数。

令大于两端数的拐点为第一类拐点,小于为第二类拐点。

情况一:不增加拐点,在dp[i-1][j]中插入数字i,我们发现第一类拐点的总数+第二类拐点的总数为j,而对于每种排列,都能让它的第一类拐点变成第二类,第二类变成第一类,如1423>>-1-4-2-3>>4132 及取负数在加上最大数再加一。因此可以证明所有情况的第一类拐点与第二类拐点的总和相同,及对于一种情况,选取第一类拐点,总有另一种情况,选择对应位置的第二类拐点(可以理解为把所有情况两两合并,要选的第一类拐点数总和就为j)。因此有2*0.5*dp[i-1][j]*j种情况,(2代表拐点两侧,0.5*dp[i-1][j]*j为所有第一类拐点的总数,)。然后还可以在两端外侧插入数字i,有dp[i-1][j]种情况。加起来就是(j+1)*dp[i-1][j]。

情况二:加一个拐点,只能在两端数的外侧和内侧加,也是两两合并的思想,有2*dp[i-1][j-1]种情况。

情况三:加两个拐点,对应dp[i-1][j-2]。同样的,总有两种情况的第一类拐点总数加起来为j-2,每个第一类拐点两端不能加故有2*(j-2)个位置不能加。两端数的外侧不能加,因此还有i-2个空位,两种情况就是2*(i-j)个空位。所以增加了0.5*dp[i-1][i-2]*(2*i-2*j)种情况。

得出状态方程:

dp[i][j] = (j+1)*dp[i-1][j] + 2*dp[i-1][j-1]
if i > j > 1:
    dp[i][j] += dp[i-1][j-2]*(i-j)

#  考虑j<2时没有可以增加两个的情况

下面是代码

n, k = map(int, input().split())
#  创建dp
dp = [[0]*k for _ in range(n)]
for i in range(1, n):
    #  这里长度其实为i+1,从长度为二的数列开始算
    #  没拐点情况就两种,单增与单减
    dp[i][0] = 2
    for j in range(1, k):
        dp[i][j] = (j+1)*dp[i-1][j] + 2*dp[i-1][j-1]
        # 特殊情况,显然i总是要大于j的,拐点数不可能比数列还长,而j-1要大于等于0
        if i > j > 1:
            dp[i][j] += dp[i-1][j-2]*(i-j)
        #  题目要求模上123456
        dp[i][j] %= 123456
print(dp[n-1][k-1])

蓝桥杯练习系统100分,看不懂可以私信问我。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值