BZOJ3622 已经没有什么好害怕的了(动态规划+容斥原理)

时间:2022-04-27 18:14:19

  显然可以转化为一个阶梯状01矩阵每行每列取一个使权值和为k的方案数。直接做不可做,考虑设f[i][j]为前i行权值和至少为j,即在其中固定了j行选1的方案数。设第i行从1~a[i]列都是1且a[i]+1列是0,则f[i][j]=f[i-1][j]+f[i-1][j-1]*(a[i]-j+1)。剩下的可以随便填,于是f[n][i]*=(n-i)!。求完之后考虑容斥,权值和恰好为x的在权值和至少为k的方案中被算了C(x,k)次,得ans=Σ(-1)i-kf[n][i]·C(i,k) (i=k~n)。

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstdlib>
#include<cstring>
#include<algorithm>
using namespace std;
int read()
{
int x=,f=;char c=getchar();
while (c<''||c>'') {if (c=='-') f=-;c=getchar();}
while (c>=''&&c<='') x=(x<<)+(x<<)+(c^),c=getchar();
return x*f;
}
#define N 2010
#define P 1000000009
int n,m,w[N],v[N],a[N],f[N][N],C[N][N],fac[N];
int main()
{
#ifndef ONLINE_JUDGE
freopen("bzoj3622.in","r",stdin);
freopen("bzoj3622.out","w",stdout);
const char LL[]="%I64d\n";
#else
const char LL[]="%lld\n";
#endif
n=read(),m=read();
for (int i=;i<=n;i++) w[i]=read();
for (int i=;i<=n;i++) v[i]=read();
fac[]=;for (int i=;i<=n;i++) fac[i]=1ll*fac[i-]*i%P;
sort(w+,w+n+),sort(v+,v+n+);
for (int i=;i<=n;i++)
for (int j=n;j;j--)
if (w[i]>v[j]) {a[i]=j;break;}
if (n+m&) {cout<<;return ;}
m=n+m>>;
f[][]=;C[][]=;
for (int i=;i<=n;i++)
{
f[i][]=f[i-][];C[i][]=C[i][i]=;
for (int j=;j<=n;j++)
f[i][j]=(f[i-][j]+1ll*f[i-][j-]*(a[i]-j+)%P)%P;
for (int j=;j<i;j++) C[i][j]=(C[i-][j-]+C[i-][j])%P;
}
int ans=;
for (int i=m;i<=n;i++)
if (i-m&) ans=(ans-1ll*f[n][i]*fac[n-i]%P*C[i][m]%P+P)%P;
else ans=(ans+1ll*f[n][i]*fac[n-i]%P*C[i][m]%P)%P;
cout<<ans;
return ;
}