bzoj3622 已经没有什么好害怕的了
原题地址:http://www.lydsy.com/JudgeOnline/problem.php?id=3622
题意:
数据范围
1≤ n ≤ 2000,0 ≤ k ≤ n
题解:
首先
k+n
k
+
n
不是偶数就无解了,让
k=(k+n)/2
k
=
(
k
+
n
)
/
2
,那么问题就是:
给两组
n
n
个数
a1...an
a
1
.
.
.
a
n
,
b1...bn
b
1
.
.
.
b
n
, 保证数字互不不相同. 问有多少种将它们配对的方式, 使得
ai>bi
a
i
>
b
i
的对数恰好为k。
如果要算恰好k对的数量,并不好算,但倘若放开范围算>=k的,就有一个比较简单的DP:
把
a[]
a
[
]
,
b[]
b
[
]
从小到大排序,
t[i]
t
[
i
]
表示
a
a
的第个比
b
b
中个大。
定义
f[i][j]
f
[
i
]
[
j
]
表示考虑了
a1...ai
a
1
.
.
.
a
i
, 在其中选出j 个, 且这j 对都满足a > b的方案数。
有
f[i][j]=f[i−1][j]+(t[i]−j+1)
f
[
i
]
[
j
]
=
f
[
i
−
1
]
[
j
]
+
(
t
[
i
]
−
j
+
1
)
而
f[n][i]∗(n−i)!
f
[
n
]
[
i
]
∗
(
n
−
i
)
!
就是>=k的方案数,恰好为i的会在其中算
(ik)次
(
i
k
)
次
令
Fi=f[n][i]∗(n−i)!
F
i
=
f
[
n
]
[
i
]
∗
(
n
−
i
)
!
,真实的恰好有i对的方案数是
gi
g
i
,有:
Fk=∑i=kn(ik)gi
F
k
=
∑
i
=
k
n
(
i
k
)
g
i
其实已经可以
n2
n
2
推了,但是么根据二项式反演有:
gk=∑i=kn(−1)i−k(ik)Fi
g
k
=
∑
i
=
k
n
(
−
1
)
i
−
k
(
i
k
)
F
i
就可以直接算了。
代码:
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int mod=1000000009;
const int N=2005;
int n,k,a[N],b[N],f[N][N],C[N][N],t[N],fac[N];
int main()
{
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
for(int j=1;j<=n;j++) scanf("%d",&b[j]);
if(n<k||(n+k)%2==1) {printf("0\n"); return 0;}
k=(n+k)/2;
sort(a+1,a+n+1); sort(b+1,b+n+1);
fac[0]=1; for(int i=1;i<=n;i++) fac[i]=(1LL*fac[i-1]*i)%mod;
for(int i=1,j;i<=n;i++) {j=1; while(j<=n&&b[j]<a[i]) j++; t[i]=j-1;}
for(int i=0;i<=n;i++)
for(int j=0;j<=i;j++)
{
if(j==0) f[i][j]=1;
else f[i][j]=(f[i-1][j]+1LL*max(0,t[i]-j+1)*f[i-1][j-1]%mod)%mod;
if(i==j||j==0) C[i][j]=1;
else C[i][j]=(C[i-1][j]+C[i-1][j-1])%mod;
}
int ans=0;
for(int i=k;i<=n;i++)
{
int w; if((i-k)%2) w=-1;else w=1;
int ret=(1LL*C[i][k]*fac[n-i])%mod;
ret=(1LL*ret*f[n][i])%mod;
ans=ans+ret*w; if(ans<0) ans+=mod;
ans%=mod;
}
printf("%d\n",ans);
return 0;
}