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
为好排列。
如果
否则,不妨设
i−1
的位置为
p
。那么我们就会发现,
dp[h]×(i−h+1)!×Ch−1i−2=dp[h]×(i−h+1)!×(i−2)!(i−h+1)!×(h−1)!=dp[h]×(i−2)!(h−1)!
综上所述,我们可以列出一个DP方程:
这里可以考虑用前缀和维护一下 dp[h](h−1)! ,这样每次转移就是 O(n) 的。这也是题解给出的递推式。
接下来转换一下思维。考虑1~i的排列中i的位置,如果i在第k位置这个排列是好的,那么就有 dp[k] 种排列,显然,每个排列的元素有 Ck−1i−1 种选法,剩余的 i−k 个元素是可以随便乱搞的,即有 (i−k)! 种排列方式。化简一下就是 dp[k]×(i−1)!(k−1)! 综上所述,我们得出一个更加优美的递推式:
可以将后面那一串用前缀和维护一下,则复杂度 O(n)
至此,该问题解决。代码之后再填吧..