cf886E Maximum Elements 题解

来源:互联网 发布:常州茗顺网络 编辑:程序博客网 时间:2024/06/05 00:11

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