题意
有 (a) 和 (b) 两个长度为 (n) 的序列,其中元素两两配对, (a>b) 的配对需比 (a<b) 的配对多恰好 (k) 个,求方案数
Solution
我们设 (f_{i,j}) 为前 (i) 个 (a) 中,选了 (j) 组 (a>b) 的方案数
可得状态转移方程:
[f_{i,j}=f_{i-1,j}+f_{i-1,j-1}*(l_i-j+1)
]
(l_i) 表示离 (a_i) 最近的一个 (b)
再设 (g_i) 为 (geq i) 个的配对方案数, (h_i) 为恰好 (=i) 个的方案数,可得 (g_i=f_{n,i}*(n-i)!)
且容易得, (f_i) 在 (g_j) 中算了 ((^i_j)) 次
所以,得到:
[g(k)=sumlimits_{i=k}^ndbinom ikf(i)
]
由二项式反演,得:
[f(k)=sumlimits_{i=k}^n(-1)^{i-k}dbinom ikg(i)
]
代码
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<iostream>
#define ll long long
using namespace std;
const int N=2005,mod=1e9+9;
int n,k,a[N],b[N];
ll f[N][N],fac[N],inv[N],fac_inv[N];
int g[N];
inline int read(){
int x=0,f=1;
char ch=getchar();
while(!isdigit(ch)){if(ch=='-') f=-1;ch=getchar();}
while(isdigit(ch)){x=x*10+(ch^48);ch=getchar();}
return x*f;
}
inline void init(){
inv[1]=1;fac[0]=fac_inv[0]=1;
for(int i=1;i<=n;i++){
if(i!=1) inv[i]=(mod-mod/i)*inv[mod%i]%mod;
fac[i]=fac[i-1]*i%mod;
fac_inv[i]=fac_inv[i-1]*inv[i]%mod;
}
}
inline ll C(int n,int m){
return fac[n]*fac_inv[m]%mod*fac_inv[n-m]%mod;
}
int main(){
n=read();k=read();
if((n+k)&1){
puts("0");
return 0;
}
k+=(n-k)/2;
for(int i=1;i<=n;i++) a[i]=read();
for(int i=1;i<=n;i++) b[i]=read();
sort(a+1,a+n+1);
sort(b+1,b+n+1);
init();
int now=0;
for(int i=1;i<=n;i++){
while(now<n&&a[i]>b[now+1]) now++;
g[i]=now;
}
for(int i=0;i<=n;i++) f[i][0]=1;
for(int i=1;i<=n;i++)
for(int j=1;j<=g[i];j++)
f[i][j]=(f[i-1][j]+f[i-1][j-1]*(g[i]-j+1)%mod)%mod;
ll ans=0;
for(int i=k;i<=n;i++)
ans=(ans+(((i-k)&1)?-1:1)*fac[n-i]*f[n][i]%mod*C(i,k)%mod+mod)%mod;
printf("%lld
",ans);
return 0;
}