【xsy2303】呀 dp

题目大意:你需要构造一个长度为$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]$

所以方案数为$\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 }
posted @ 2019-04-27 08:21  AlphaInf  阅读(161)  评论(0编辑  收藏  举报