题目大意:你需要构造一个长度为$n$的排列$A$,使得里面包含有子序列$B$(子序列$B$为一个给定的$1$到$m$的排列),且对于每个$i$,有$A[A[i]]=i$,问有多少种方案方案。
数据范围:$n≤10^7$,$m≤500$,答案对$10^9+7$取模
我们首先不考虑有m的存在,考虑如何构造一个符合条件的序列$A$。
我们发现我们可以DP,设$f[i]$表示有多少种长度为i的序列满足$A[A[i]]=i$。
对于第$i$个数,我们可以考虑把它填在原位,或者放在第j个位置,然后在$A[i]$处填上$j$。
根据这个,不难推出$f[i]=(i-1)f[i-2]+f[i-1]$。
我们下面考虑,序列$B$的前$k$个数,位于$A$中前$m$个位置,剩余的$m-k$个,位于后$n-m$个位置。
我们发现,对于剩余的$m-k$个数,对应的$A_B[i]$,都会被占用,用来填(填写了$B[i]$的位置)。
所以我们需要在前$m$个位置里,用恰好$k$个位置,按顺序填出序列$B$前$k$个数字。
不难发现,至多只有一种填法。
对于$B[i]$,我们找到序列$A$中第i个可以填数的地方,直接填入$B[i]$即可。
最后$O(m)$扫一遍判断即可。
如果可行,那么剩下的$n-k$个位置中,需要找出$m-k$个位置放置$B[k+1],B[k+2].....B[m]$,并且在$A[B[k+1]]$等地方填上它们的位置。
剩下的$n-2m+k$个位置,就可以随便填>m的数了,方案数显然是$f[n-2m+k]$
所以方案数为$inom{n-k}{m-k} imes f[n-2m+k]$。
我们对于每个不超过m的k全部处理一遍就可以了。
时间复杂度:$O(n+m^2)$
1 #include<bits/stdc++.h> 2 #define MOD 1000000007 3 #define L long long 4 #define M 10000005 5 using namespace std; 6 7 L fac[M]={0},invfac[M]={0},f[M]={0}; 8 L pow_mod(L x,L k){L ans=1;for(;k;k>>=1,x=x*x%MOD) if(k&1) ans=ans*x%MOD; return ans;} 9 L C(int n,int m){return fac[n]*invfac[m]%MOD*invfac[n-m]%MOD;} 10 11 L mark[M]={0},b[M]={0},a[M]={0},ans=0,n,m; 12 void solve(int k){ 13 if(n-m<m-k) return; 14 for(int i=1,j=1;i<=k;i++,j++){ 15 while(!mark[j]) j++; 16 a[j]=b[i]; 17 } 18 for(int i=1;i<=m;i++) 19 if(mark[i]&&a[a[i]]!=i) return; 20 ans=(ans+C(n-m,m-k)*f[n-m-m+k])%MOD; 21 } 22 int main(){ 23 fac[0]=1; for(int i=1;i<M;i++) fac[i]=fac[i-1]*i%MOD; 24 invfac[M-1]=pow_mod(fac[M-1],MOD-2); 25 for(int i=M-2;~i;i--) invfac[i]=invfac[i+1]*(i+1)%MOD; 26 f[0]=f[1]=1; for(int i=2;i<M;i++) f[i]=(f[i-1]+f[i-2]*(i-1))%MOD; 27 28 scanf("%d%d",&n,&m); 29 for(int i=1;i<=m;i++) scanf("%d",b+i); 30 for(int k=0;k<=m;k++){ 31 mark[b[k]]=1; 32 solve(k); 33 } 34 cout<<ans<<endl; 35 }