CF886E Maximum Element dp 组合数 前缀和

本文详细解析了一道洛谷竞赛题目,题目要求计算对于给定序列的所有排列中,使用特定方法找最大值失败的情况数量。文章提供了算法思路,采用动态规划方法计算方案数,最终通过代码实现解决问题。

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

题目链接
链接是洛谷有翻译的。

题意:
有一个长度为 n n n的序列,序列的数互不相同。有个人要求这个排列的最大值,他的做法是如果出现了当前值比已经出现过的值都大就更新答案,否则累加一个计数器,如果连续k个数都没有比之前最大值大,那么他就认为当前的最大值就是最后的最大值。问你对于这个序列的所有排列,这种方法有多少种情况下是错的。对1e9+7取模。 n , k &lt; = 1 e 6 n,k&lt;=1e6 n,k<=1e6

题解:
我们考虑用总的方案数减去所有能用这个方法找到最大值的方案数来求出最终的答案。总方案就是一个 n ! n! n!很好求,那么我们就考虑能这样找到最大值的方案数。我们发现其实这个每一个数都不同的序列可以把它看作一个 1 1 1 n n n的排列,因为我们并不关心每个数是什么,只关心一个相对大小关系。我们发现,最大数 n n n出现的条件是在它出现之前没有直接退出。那么我们设 d p [ i ] dp[i] dp[i]表示前 i i i个数组成的排列处理到第 i i i个位置没有退出的方案数。

由于没有退出,所以 1 − i 1-i 1i的最大值一定会出现,那么我们枚举这个最大值出现的位置,不难发现,最大值出现的位置应该在 [ i − k + 1 , i ] [i-k+1,i] [ik+1,i]才会保证不中途退出。我们枚举这个最大值出现的位置 j j j,要求前面的 j − 1 j-1 j1个位置也要没有退出,后面从 i − 1 i-1 i1个数中选出 i − j i-j ij个数,并且这 i − j i-j ij个位置的数可以随便排列,于是有
d p [ i ] = ∑ j = m a x ( 0 , i − k + 1 ) i − 1 d p [ j − 1 ] ∗ C i − 1 i − j ∗ ( i − j ) ! dp[i]=\sum_{j=max(0,i-k+1)}^{i-1}dp[j-1]*C_{i-1}^{i-j}*(i-j)! dp[i]=j=max(0,ik+1)i1dp[j1]Ci1ij(ij)! 把组合数拆成阶乘 d p [ i ] = ∑ j = m a x ( 0 , i − k + 1 ) i − 1 d p [ j − 1 ] ∗ ( i − 1 ) ! ( i − j ) ! ( j − 1 ) ! ∗ ( i − j ) ! = ∑ j = m a x ( 0 , i − k + 1 ) i − 1 d p [ j − 1 ] ∗ ( i − 1 ) ! ( j − 1 ) ! dp[i]=\sum_{j=max(0,i-k+1)}^{i-1}dp[j-1]*\frac{(i-1)!}{(i-j)!(j-1)!}*(i-j)!=\sum_{j=max(0,i-k+1)}^{i-1}dp[j-1]*\frac{(i-1)!}{(j-1)!} dp[i]=j=max(0,ik+1)i1dp[j1](ij)!(j1)!(i1)!(ij)!=j=max(0,ik+1)i1dp[j1](j1)!(i1)! ( i − 1 ) ! (i-1)! (i1)!提出 ∑ j = m a x ( 0 , i − k + 1 ) i − 1 d p [ j − 1 ] ∗ ( i − 1 ) ! ( j − 1 ) ! = ( i − 1 ) ! ∑ j = m a x ( 0 , i − k + 1 ) i − 1 d p [ j − 1 ] ( j − 1 ) ! \sum_{j=max(0,i-k+1)}^{i-1}dp[j-1]*\frac{(i-1)!}{(j-1)!}=(i-1)!\sum_{j=max(0,i-k+1)}^{i-1}\frac{dp[j-1]}{(j-1)!} j=max(0,ik+1)i1dp[j1](j1)!(i1)!=(i1)!j=max(0,ik+1)i1(j1)!dp[j1] 我们发现这个式子我们可以维护一个 d p [ i − 1 ] ( i − 1 ) ! \frac{dp[i-1]}{(i-1)!} (i1)!dp[i1]的前缀和,这样就可以 O ( 1 ) O(1) O(1)转移了。

这样我们就用 O ( n ) O(n) O(n)的时间算出了有多少个长度为 i i i排列没有提前结束。那么在算最终答案的时候,我们用一个类似的dp方程式来算。我们是用所有方案减去能找到最大值的方案。我们设最大值出现在第 i i i个位置,那么能找到最大值就要求前面的没有提前结束,后面的是从 n − 1 n-1 n1个数中随便选 n − i n-i ni个,并且这 n − i n-i ni个数可以随便排列。那么我们最终的答案就是 a n s = n ! − ∑ i = 1 n d p [ i − 1 ] ∗ C n − 1 n − i ∗ ( n − i ) ! ans=n!-\sum_{i=1}^ndp[i-1]*C_{n-1}^{n-i}*(n-i)! ans=n!i=1ndp[i1]Cn1ni(ni)! 这个式子也可以 O ( n ) O(n) O(n)算出。于是总复杂度是 O ( n ) O(n) O(n)

代码:

#include <bits/stdc++.h>
using namespace std;

int n,k;
long long ni[1000010],dp[1000010],jie[1000010],ans,s[1000010];
const long long mod=1e9+7;
inline long long ksm(long long x,long long y)
{
	long long res=1;
	while(y)
	{
		if(y&1)
		res=res*x%mod;
		x=x*x%mod;
		y>>=1;
	}
	return res;
}
int main()
{
	scanf("%d%d",&n,&k);
	if(n<=k+1)
	{
		printf("0\n");
		return 0;
	}
	jie[0]=1;
	for(int i=1;i<=n;++i)
	jie[i]=jie[i-1]*i%mod;
	ni[n]=ksm(jie[n],mod-2);
	for(int i=n-1;i>=0;--i)
	ni[i]=ni[i+1]*(i+1)%mod;
	dp[0]=1;
	s[0]=1;
	for(int i=1;i<=n;++i)
	{
		if(i-k-1>=0)
		dp[i]=jie[i-1]*(s[i-1]-s[i-k-1]+mod)%mod;
		else
		dp[i]=jie[i-1]*s[i-1]%mod;
		s[i]=(s[i-1]+dp[i]*ni[i]%mod)%mod;
	}
	ans=jie[n];
	for(int i=1;i<=n;++i)
	ans=(ans-dp[i-1]*jie[n-1]%mod*ni[i-1]%mod+mod)%mod;
	printf("%lld\n",ans);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值