题解 BZOJ3622【已经没有什么好害怕的了】

前置知识:二项式反演

problem

两个 \(n\leq 2000\) 的数组 \(A,B\),元素互不相同,求有多少种将 \(A,B\) 配对的方法使得 \(A>B\) 的恰好有 \(k\) 对。

题目改过,但是这一步转换很简单。

solution

恰好很难算。我们算至少:\(g(t)\) 表示钦定 \(t\) 个 pair 是大于有多少种方案,\(f(t)\) 表示恰好有 \(t\) 个 pair 是大于有多少种方案。

二项式反演:

\[g(k)=\sum_{k\leq i\leq n}\binom{i}{k}f(i)\Leftrightarrow f(k)=\sum_{k\leq i\leq n}(-1)^{i-k}\binom{i}{k}g(i). \]

但是这个题的难点不在这里,我们要算 \(g\),怎么算呢?DP。

首先 \(A,B\) 排序对结果无影响,然后二分求出 \(h_i=\sum_j[b_j<a_i]\) 表示有多少个 \(b\) 小于 \(a\)

\(f_{i,j}\) 表示考虑到 \(a_i\),前面有 \(j\) 个已经匹配。

  • \(i\) 不匹配:\(f_{i-1,j}\)
  • \(i\) 匹配:\((h_i-j+1)f_{i-1,j-1}\),这表示 \(i\) 的匹配对象中,\(j-1\) 个已经被用了,其他的随便挑一个。

然后剩下的随机匹配,得到 \(g(t)=f_{n,j}(n-j)!\)

code

点击查看代码
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#ifdef LOCAL
#define debug(...) fprintf(stderr,##__VA_ARGS__)
#else
#define debug(...) void(0)
#endif
typedef long long LL;
const int P=1e9+9;
LL mod(LL x){return (x%P+P)%P;}
void red(LL&x){x=mod(x);}
LL qpow(LL a,LL b,int p=P){LL r=1;for(a%=p;b;b>>=1,a=a*a%p) if(b&1) r=r*a%p; return r;}
template<int N,int P> struct C_prime{
	LL fac[N+10],ifac[N+10];
	C_prime(){
		for(int i=fac[0]=ifac[0]=1;i<=N;i++) fac[i]=fac[i-1]*i%P;
		ifac[N]=qpow(fac[N],P-2,P);
		for(int i=N-1;i>=1;i--) ifac[i]=ifac[i+1]*(i+1)%P;
	}
	LL operator()(int n,int m){return n>=m?fac[n]*ifac[n-m]%P*ifac[m]%P:0;}
};
int n,k,a[2010],b[2010],h[2010],g[2010];
LL f[2010][2010],fac[2010],ans;
C_prime<2010,P> C;
void dp(){
	f[0][0]=1;
	for(int i=1;i<=n;i++){
		for(int j=0;j<=i;j++){
			f[i][j]=f[i-1][j];
			if(j&&h[i]>=j-1) red(f[i][j]+=f[i-1][j-1]*(h[i]-j+1));
		}
	}
	for(int j=0;j<=n;j++) g[j]=f[n][j]*fac[n-j]%P;
}
int main(){
	for(int i=fac[0]=1;i<=2000;i++) fac[i]=fac[i-1]*i%P;
//	#ifdef LOCAL
//		freopen("input.in","r",stdin);
//	#endif
	scanf("%d%d",&n,&k);
	if((n+k)&1) return puts("0"),0;
	k=(n+k)/2;
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	for(int i=1;i<=n;i++) scanf("%d",&b[i]);
	sort(a+1,a+n+1),sort(b+1,b+n+1);
	for(int i=1;i<=n;i++) h[i]=lower_bound(b+1,b+n+1,a[i])-b-1;
	dp();
	for(int i=k;i<=n;i++) red(ans+=C(i,k)*((i-k)&1?-1:1)*g[i]);
	printf("%lld\n",ans);
	return 0;
}
//2x-n=k => x=(n+k)/2
//f[i][j]=f[i-1][j]+f[i-1][j-1]*(h[i]-(j-1))
posted @ 2023-01-11 11:52  caijianhong  阅读(136)  评论(0编辑  收藏  举报