【xsy2303】呀 dp

題目大意:你須要構造一個長度爲$n$的排列$A$,使得裏面包含有子序列$B$(子序列$B$爲一個給定的$1$到$m$的排列),且對於每一個$i$,有$A[A[i]]=i$,問有多少種方案方案。c++

數據範圍:$n≤10^7$,$m≤500$,答案對$10^9+7$取模spa

 

咱們首先不考慮有m的存在,考慮如何構造一個符合條件的序列$A$。code

咱們發現咱們能夠DP,設$f[i]$表示有多少種長度爲i的序列知足$A[A[i]]=i$。blog

對於第$i$個數,咱們能夠考慮把它填在原位,或者放在第j個位置,而後在$A[i]$處填上$j$。it

根據這個,不難推出$f[i]=(i-1)f[i-2]+f[i-1]$。class

 

咱們下面考慮,序列$B$的前$k$個數,位於$A$中前$m$個位置,剩餘的$m-k$個,位於後$n-m$個位置。im

咱們發現,對於剩餘的$m-k$個數,對應的$A_B[i]$,都會被佔用,用來填(填寫了$B[i]$的位置)。數據

因此咱們須要在前$m$個位置裏,用剛好$k$個位置,按順序填出序列$B$前$k$個數字。di

不難發現,至多隻有一種填法。時間

對於$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]$

因此方案數爲$\binom{n-k}{m-k}\times 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 }
相關文章
相關標籤/搜索