【CodeForces】889 C. Maximum Element 排列组合+动态规划

本文介绍了一种使用排列组合与动态规划解决好的排列计数问题的方法。通过定义好的排列,并利用D(n)表示长度为n且满足特定条件的排列数量,推导出了计算公式。最终实现了O(n)复杂度的解决方案。

【题目】C. Maximum Element

【题意】给定n和k,定义一个排列是好的当且仅当存在一个位置i,满足对于所有的j=[1,i-1]&&[i+1,i+k]有a[i]>a[j],求长度为n的好的排列数。n<=10^6。

【算法】排列组合+动态规划

【题解】设D(n)表示长度为n且满足a[n]=n的好的排列数,考虑这样的一个排列w。

如果数字n-1的位置j<n-k,那么显然这是一个好的排列。

如果数字n-1的位置j>=n-k,那么位置j前的数字一定<n-1,那么1~j形成好的排列的方案实际上是D(j)。

$$D(n)=(n-k-1)*(n-2)!+\sum_{j=n-k}^{n-1}D(j)*A(n-2,n-j-1)$$

第一部分:数字n-1有n-k-1个位置,每个位置固定后可以进行全排列即(n-2)!

第二部分:枚举数字n-1的位置,固定后后面的n-j-1个位置可以从除了n和n-1的数字中任意取数填满,剩下的数字当成1~j构成D(j)。

化简后得到:

$$D(n)=(n-k-1)*(n-2)!+(n-2)!\sum_{j=n-k}^{n-1}\frac{D(j)}{(j-1)!}$$

边算前缀和即可。

最后,枚举答案中n的位置(因为数字n后面的位置没有意义),那么:

$$ans=\sum_{i=1}^{n}D(j)*A(n-1,n-i)=(n-1)!*\sum_{i=1}^{n}\frac{D(j)}{(j-1)!}$$

复杂度O(n)。

#include<cstdio>
const int maxn=1000010,MOD=1e9+7;
int n,k,fac[maxn],fav[maxn],D[maxn],h[maxn];
void gcd(int a,int b,int& x,int& y){if(!b){x=1;y=0;}else{gcd(b,a%b,y,x);y-=x*(a/b);}}
int inv(int a){int x,y;gcd(a,MOD,x,y);return (x%MOD+MOD)%MOD;}
int main(){
    scanf("%d%d",&n,&k);
    fac[0]=1;
    for(int i=1;i<=n;i++)fac[i]=1ll*fac[i-1]*i%MOD,fav[i]=inv(fac[i]);
    for(int i=1;i<=n;i++)if(i>k){
         D[i]=1ll*fac[i-2]*((i-k-1)+h[i-1]-h[i-k-1]+MOD)%MOD;
         h[i]=(h[i-1]+1ll*D[i]*fav[i-1])%MOD;
    }
    printf("%lld",1ll*h[n]*fac[n-1]%MOD);
    return 0;
}
View Code

 

排列组合相关的DP需要记住一件事:1~n的排列代表的是n个数的大小关系的排列,不一定需要1~n,然后就可以很方便地转移了。

转载于:https://www.cnblogs.com/onioncyc/p/8576598.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值