CF886E Maximum Element(dp、组合数学)

博客内容介绍了如何使用动态规划方法解决一个关于排列循环且不允许元素'跳出'的数学问题。通过转移方程和前缀和优化,线性时间内求解方案数,并给出C++代码实现。

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

Solution\text{Solution}Solution

纯纯的dp题。
关键在于我们 dp 时只关注不同元素之间的相对大小

非法情况不易统计,考虑转而考虑合法情况再用全排列减。
设计 fif_ifi 为长度为 iii 的排列循环到一直最后也没有跳出的方案数。
枚举最大的元素 iii 放置的位置 jjj,由于不能跳出,jjj 只能放在[i−k+1,i][i-k+1,i][ik+1,i] 的位置。
那么就有:
fi=∑j=i−k+1i(i−1j−1)×fj−1×(j−i)!f_i=\sum_{j=i-k+1}^i \binom{i-1}{j-1}\times f_{j-1}\times (j-i)!fi=j=ik+1i(j1i1)×fj1×(ji)!
解释一下,当元素 iii 放在位置 jjj 时,选出 j−1j-1j1 个数放前面,方案是(i−1j−1)\binom{i-1}{j-1}(j1i1);前面的方案就是 fj−1f_{j-1}fj1(和排列是等价的),后面随便放,方案就是阶乘。
然后把组合数拆一下,变成:
fi=∑j=i−k+1i(i−1)!(j−1)!×fj−1f_i=\sum_{j=i-k+1}^i\frac{(i-1)!}{(j-1)!} \times f_{j-1}fi=j=ik+1i(j1)!(i1)!×fj1
=(i−1)!×∑j=i−k+1ifj−1(j−1)!=(i-1)!\times\sum_{j=i-k+1}^i\frac{f_{j-1}}{(j-1)!} =(i1)!×j=ik+1i(j1)!fj1
=(i−1)!×∑j=i−ki−1fjj!=(i-1)!\times\sum_{j=i-k}^{i-1}\frac{f_{j}}{j!} =(i1)!×j=iki1j!fj
把后面的东西拿前缀和优化一下即可线性求出 fff 数组。
求出 fff 之后,枚举 nnn 所在的位置 iii,答案就是:
ans=∑i=1n(n−1i−1)×fi−1×(n−i)!ans=\sum_{i=1}^n\binom{n-1}{i-1}\times f_{i-1}\times (n-i)!ans=i=1n(i1n1)×fi1×(ni)!

Code\text{Code}Code

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
#define debug(...) fprintf(stderr,__VA_ARGS__)
inline ll read(){
  ll x(0),f(1);char c=getchar();
  while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
  while(isdigit(c)){x=(x<<1)+(x<<3)+c-'0';c=getchar();}
  return x*f;
}


const int N=1e6+100;
const int mod=1e9+7;
int n,m,k;
inline ll ksm(ll x,ll k){
  ll res(1);
  while(k){
    if(k&1) res=x*res%mod;
    x=x*x%mod;
    k>>=1;
  }
  return res;
}
ll jc[N],ni[N];
ll f[N],sum[N];
inline ll C(ll n,ll m){
  return jc[n]*ni[m]%mod*ni[n-m]%mod;
}
signed main(){
#ifndef ONLINE_JUDGE
  freopen("a.in","r",stdin);
  freopen("a.out","w",stdout);
#endif
  n=read();k=read();
  jc[0]=1;
  for(int i=1;i<=n;i++) jc[i]=jc[i-1]*i%mod;
  ni[n]=ksm(jc[n],mod-2);
  for(int i=n-1;i>=0;i--) ni[i]=ni[i+1]*(i+1)%mod;
  f[0]=1;sum[0]=1;
  for(int i=1;i<=n;i++){
    f[i]=jc[i-1]*(sum[i-1]+mod-(i-k>0?sum[i-k-1]:0))%mod;
    sum[i]=(sum[i-1]+f[i]*ni[i])%mod;
  }
  ll ans(0);
  for(int i=1;i<=n;i++){
    (ans+=f[i-1]*C(n-1,i-1)%mod*jc[n-i]%mod)%=mod;
  }
  printf("%lld\n",(jc[n]+mod-ans)%mod);
  return 0;
}
/*
*/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值