cf886E Maximum Elements 题解

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)
至此,该问题解决。代码之后再填吧..

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值