cf886E Maximum Elements 题解

本文解析了一道DP题目,通过巧妙的状态设计将问题转化为组合数学问题。利用动态规划思想,通过分类讨论,给出了两种不同的DP方程,最终实现O(n)的时间复杂度。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

DP神题。也是本蒟蒻做的第一道组合数学+DP题。


先来看看题意。Petya嫌弃自己求区间最大值的做法太慢了,所以发明了这样一个函数:

int fast_max(int n, int a[]) { 
    int ans = 0;
    int offset = 0;
    for (int i = 0; i < n; ++i)
        if (ans < a[i]) {
            ans = a[i];
            offset = 0;
        } else {
            offset = offset + 1;
            if (offset == k)
                return ans;
        }
    return ans;
}

显然这个函数很容易被hack掉,现在简化问题:a数组是1~n的排列,输入n,k,问有多少情况会被hack掉。
比如说,n = 5, k = 3,那么[4, 1, 2, 3, 5], [4, 1, 3, 2, 5], [4, 2, 1, 3, 5], [4, 2, 3, 1, 5], [4, 3, 1, 2, 5], [4, 3, 2, 1, 5]是会被hack掉的。


一上来我看见了dp的标签,就在思考前i个数能够hack掉的情况有多少。但这样转移是很难设计的,于是我就厚颜无耻的看了题解。鉴于我辣鸡的阅读理解水平,还是没有理解。
于是我机智的翻了讨论区。
首先,我们用dp[i]表示1~i的排列能够hack掉的数量是多少。
不得不说,这是一个非常巧妙的状态设计,因为借此问题就从一道玄学题变成了组合数学题。
再思考转移。那么目前横亘在我们面前的就是:i是否被作为最大值扔出去?
分类讨论一下。
我们称一个符合条件的排列w为好排列。
如果i1在前ik1个数中,那么这种情况有(ik1)×(i2)!,因为i1这个数有ik1种摆法,而前i2个数可以随心情乱摆。
否则,不妨设i1的位置为p。那么我们就会发现,i1已经救不了这一排列了,我们必须让前h个数成为一个好的排列,则共有DP[h]种情况。而剩下ih+1个可以随便放,有(ih+1)!种方法。显然,前h个数有Ch1i2种选法,然后稍作变形,有:
dp[h]×(ih+1)!×Ch1i2=dp[h]×(ih+1)!×(i2)!(ih+1)!×(h1)!=dp[h]×(i2)!(h1)!

综上所述,我们可以列出一个DP方程:

dp[i]=(ik1)×(i2)!+h=iki1dp[h]×(i2)!(h1)!

这里可以考虑用前缀和维护一下dp[h](h1)!,这样每次转移就是O(n)的。这也是题解给出的递推式。
接下来转换一下思维。考虑1~i的排列中i的位置,如果i在第k位置这个排列是好的,那么就有dp[k]种排列,显然,每个排列的元素有Ck1i1种选法,剩余的ik个元素是可以随便乱搞的,即有(ik)!种排列方式。化简一下就是dp[k]×(i1)!(k1)!综上所述,我们得出一个更加优美的递推式:
dp[i]=k=1idp[k]×(i1)!(k1)!

可以将后面那一串用前缀和维护一下,则复杂度O(n)
至此,该问题解决。代码之后再填吧..
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值