1. 程式人生 > 其它 >[USACO20DEC] Sleeping Cows P

[USACO20DEC] Sleeping Cows P

題目傳送門

分析

將牛和牛棚從小到大排序。

對於存在沒有牛棚的牛的合法方案

考慮列舉最小的沒有牛棚的牛 \(i\) ,以及找到最小可以容納牛 \(i\) 的牛棚 \(j\)

容易得到如下性質:

  1. 對於 牛\(1\) ~ 牛\(i\) ,必定都擁有對應牛棚;
  2. 對於 牛棚\(j\) ~ 牛棚\(n\) ,必定都擁有對應牛。

可以預處理陣列 \(F_{i,j}\)\(G_{i,j}\) ,其中 \(F_{i,j}\) 表示 牛\(i\) ~ 牛\(n\) 匹配 \(j\) 個牛棚的方案數, \(G_{i,j}\) 表示 牛棚\(1\) ~ 牛棚\(i\) 匹配 \(j\) 個牛的方案數。

列舉 牛\(i\) 的時候,列舉 牛\(1\) ~ 牛\(i-1\) 中有 \(k\) 頭牛與 牛棚\(j\) ~ 牛棚\(n\) 匹配。

因為顯然 牛\(i+1\) ~ 牛\(n\) 只會與 牛棚\(j\) ~ 牛棚\(n\) 匹配 ,牛棚\(1\) ~ 牛棚\(j-1\) 只會與 牛\(1\) ~ 牛\(i-1\) 匹配

而這 \(k\) 頭牛在空出來的地方是可以隨便排的,故還要乘上 \(k!\)

那麼對於 牛\(i\) 的答案,即為 \(\sum^{min(i-1,n-j+1)}_{k=0} G[j-1][i-1-k] \times F[i+1][n-j+1-k] \times k!\)

對於所有牛都有與之對應牛棚的合法方案

答案即為 \(F_{1,n}\)

程式碼

#include<cstdio>
#include<algorithm>
#include<iostream>
#include<cstring>
#define M 3005
const int P=1e9+7;
using namespace std;
typedef long long ll;
typedef double db;
char IO;
int rd(){
	int num=0;bool f=0;
	while(IO=getchar(),IO<48||IO>57)if(IO=='-')f=1;
	do num=(num<<1)+(num<<3)+(IO^48);
	while(IO=getchar(),IO>=48&&IO<=57);
	return f?-num:num;
}
int n;
int S[M],T[M],cnt[M];
ll F[M][M],G[M][M],Fac[M];
int main(){
	n=rd();
	Fac[0]=1;for(int i=1;i<=n;++i)Fac[i]=Fac[i-1]*i%P;
	for(int i=1;i<=n;++i)S[i]=rd();
	for(int i=1;i<=n;++i)T[i]=rd();
	sort(S+1,S+n+1);sort(T+1,T+n+1);
	for(int i=1;i<=n;++i)
		cnt[i]=n-(lower_bound(T+1,T+n+1,S[i])-T)+1;
	F[n+1][0]=1;// i-n 牛 匹配 j 個牛棚 
	for(int i=n;i;--i){
		for(int j=cnt[i];~j;--j){
			if(j)(F[i][j]+=F[i+1][j-1]*(cnt[i]-j+1))%=P;
			(F[i][j]+=F[i+1][j])%=P;
		}
	}
	G[0][0]=1;// 1-i 牛棚 匹配 j 個牛 
	for(int i=1,lim;i<=n;++i){
		lim=upper_bound(S+1,S+n+1,T[i])-S-1;
		for(int j=0;j<=lim;++j){
			if(j)(G[i][j]+=G[i-1][j-1]*(lim-j+1))%=P;
			(G[i][j]+=G[i-1][j])%=P;
		}
	}
	ll ans=F[1][n];
	for(int i=1,lim;i<=n;++i){
		lim=lower_bound(T+1,T+n+1,S[i])-T;
		for(int j=0;j<=min(i-1,n-lim+1);++j)
			(ans+=G[lim-1][i-1-j]*F[i+1][n-lim+1-j]%P*Fac[j])%=P;
	}
	cout<<ans;
	return 0;
}