【DP+高精度+压位+容斥】【SHOI2009 舞会】

题目描述

OItown要举办了一年一度的超级舞会了,作为主办方的Constantine为了使今年的舞会规模空前,他邀请了许多他的好友和同学去。舞会那天,恰好来了n个男生n个女生。Constantine发现,一般情况下,舞伴之间,总是男伴总是比女伴长得高,不过,偶尔也是有特殊情况的。所以,Constantine现在想知道,如果把这2n个人恰好配成n对舞伴,有多少种搭配方法,而且他要求最多只有k对舞伴之间女伴比男伴高。现在,Constantine需要参加SHTSC的你帮助他算出这个答案,当然啦,他会先告诉你这2n个同学的身高。

输入输出格式

输入格式:第一行:两个整数n、k,含义如问题中所示。 第2行到第n+1行:n个整数,表示n个男生的身高。 第n+2行到第2n+1行:n个整数,表示n个女生的身高。 表示身高的正整数,都不超过 。

输出格式:输出文件只有一个,即满足n对舞伴中最多只有k对舞伴之间女伴比男伴高的男女搭配方案数。

输入输出样例

输入样例#1: 复制

3 0
178
188
176
168
178
170

输出样例#1: 复制

4

说明

评分 如果你的输出文件与标准答案完全相符,你将获得该测试点的全部分数,否则得零分。

N< = 200

K< = N

【思路】好难啊。。看了几个题解。。简单总结一下。。

首先把男生和女生的身高排个序。

对于匹配的过程,我们可以把女生看做是固定的,男生去和女生匹配。

dp[i][j]表示前i个女生中已经确定有j个女生比对应的男生高的情况下,这j个女生的配对情况数其它女生暂时不考虑

令p表示:总共有p个男生比第i个女生低。

转移就是dp[i][j]=dp[i-1][j]+dp[i-1][j-1]*(p-(j-1))

解释一下:对于dp[i][j],有两个部分:第一个部分是这j个女生全部在前(i-1)个女生里面,那么配对数就是dp[i-1][j]。另一个部分是有一个女生在第i个,其余(j-1)个女生在前(i-1)个里面。那么对于第i个女生,她需要跟一个比她矮的男生配对。由于已经有(j-1)个男生跟前面的配对了,那么剩下能和她配对的就只有这(p-(j-1))个了。

所以,第二个部分的方案数就是dp[i-1][j-1]*(p-(j-1))

合起来就是dp[i][j]=dp[i-1][j]+dp[i-1][j-1]*(p-(j-1))

然而对于dp[n][j],只考虑了j对【女高于男】的情况,对于剩下的(n-j)对还没算。然而他们瞎配对就行了。。

一共就是dp[n][i]=dp[n][i]*(n-i)!种方案。。

【下文中,几对指的是女生比男生高有几对】

然而dp[n][i]并不是至少有i对的情况,其中包含一些重复的。如下:

参考博客

这里写图片描述

其中红色线为原本已确定的女比男高的配对。

比如至少要有2对,那么这几种【2对】就会对应出相同的几种配对【因为其他人是随便配对的】。dp[n][2]就把这几个相同的配对算重复了。

【令g[i]表示恰好有i对的情况】

那么对于dp[n][i],在一个j(j∈[i,n])对的方案中选i对出来,每种选法都会使 dp[n][i] 被贡献一次。

所以    g[j]\times\textrm{C}_{j}^{i}    就是恰好有j对的情况在dp[n][i]中被统计的总次数。

然而对于恰好有i对的情况,dp[n][i]就只算了一次。因为\textrm{C}_{i}^{i}=1

那就得到一个公式:g[i]=dp[n][i]-\sum_{j=i+1}^{n}\textrm{C}_{j}^{i}\times g[j]

那么就可以倒着推一下,因为dp[n][n]就表示至少有i对的情况,也就是恰好有i对的情况。所以g[n]=dp[n][n]。

然后往前面算出可以算出恰好(n-1)对,恰好(n-2)对...减完过后dp[n][i]就表示了恰好有i对的情况。

注意一下,高精度要压位,不然空间要爆......

【范围是n<=200...我以为是2n<=200...调了一天...】

【代码】:

#include<bits/stdc++.h>
using namespace std;
const int R=1e4;
int boy[205],girl[205];
int n,m,now=0;
struct BIG{
	int s[110];
	int len;
	BIG(){memset(s,0,sizeof(s));len=0;}
	friend inline BIG operator *(const BIG &a,const BIG &b){
		BIG c;
		c.len=a.len+b.len+1;
		for(int i=0;i<=a.len;++i){
			for(int j=0;j<=b.len;++j){
				c.s[i+j+1]+=a.s[i]*b.s[j]/R;
				c.s[i+j]+=a.s[i]*b.s[j]%R;
			}
		}
		for(int i=0;i<=c.len;i++){
			c.s[i+1]+=c.s[i]/R;
			c.s[i]%=R;
		}
		while((!c.s[c.len])&&c.len>0) c.len--;
		return c;
	}
	friend inline BIG operator *(const BIG &a,const int &b){
		BIG c;
		c.len=a.len+5;
		for(int i=0;i<=a.len;++i)
			c.s[i]+=a.s[i]*b;
		for(int i=0;i<=c.len;i++){
			c.s[i+1]+=c.s[i]/R;
			c.s[i]%=R;
		}
		while((!c.s[c.len])&&c.len>0) c.len--;
		return c;
	}
	friend inline BIG operator +(const BIG &a,const BIG &b){
		BIG c;
		c.len=max(a.len,b.len)+1;
		for(int i=0;i<=c.len;++i)
			c.s[i]=a.s[i]+b.s[i];
		for(int i=0;i<=c.len;++i){
			c.s[i+1]+=c.s[i]/R;
			c.s[i]%=R;
		}
		while((!c.s[c.len])&&c.len>0) c.len--;
		return c;
	}
	friend inline BIG operator -(const BIG &a,const BIG &b){
		BIG c;
		c.len=max(a.len,b.len);
		for(int i=0;i<=c.len;++i)
			c.s[i]=a.s[i]-b.s[i];
		for(int i=0;i<=c.len;++i)
			if(c.s[i]<0) c.s[i]+=R,c.s[i+1]-=1;
		while((!c.s[c.len])&&c.len>0) c.len--;
		return c;
	}
	void print(){
		printf("%d",s[len]);
		for(int i=len-1;i>=0;i--)
			printf("%04d",s[i]);
	}
}dp[205][205],C[205][205],jc[205],one,zero,ans;
inline int read(){
	int x=0;char ch=getchar();
	while(!isdigit(ch)) ch=getchar();
	while(isdigit(ch)) x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
	return x;
}
inline void init(){
	one.s[0]=1,zero.s[0]=0;
	dp[0][0]=jc[0]=C[0][0]=one;
	ans=zero;
	for(int i=1;i<=200;++i) jc[i]=jc[i-1]*i;
	for(int i=1;i<=200;++i){
		C[i][0]=one;
		for(int j=0;j<=i;++j)
            //组合数预处理,C[i][j]就是从i个里面选j个
            //即不选第j个(C[i-1][j])和选了第j个(C[i-1][j-1])的方案数之和
			C[i][j]=C[i-1][j]+C[i-1][j-1];
	}
}
int main(){
	init();
	n=read(),m=read();
	for(int i=1;i<=n;++i) boy[i]=read();
	for(int i=1;i<=n;++i)girl[i]=read();
	sort(boy+1,boy+n+1),sort(girl+1,girl+n+1);
	for(int i=1;i<=n;++i){
		while(now<n&&boy[now+1]<girl[i]) now++;//now记录总共有几个男生比第i个女生矮
		for(int j=0;j<=i;++j)
			dp[i][j]=dp[i-1][j]+dp[i-1][j-1]*(now-(j-1));
	}
	for(int i=0;i<=n;++i) dp[n][i]=dp[n][i]*jc[n-i];

    //dp[n][n]表示前n对中有n对女生高于男生,这个没有重复算,相当于g[n]。
	for(int i=n-1;i>=0;--i)
		for(int j=i+1;j<=n;++j)
			dp[n][i]=dp[n][i]-C[j][i]*dp[n][j];//减完过后,dp[n][i]就表示g[i]。
	for(int i=0;i<=m;++i)
		ans=ans+dp[n][i];
	ans.print();
}

猜你喜欢

转载自blog.csdn.net/g21wcr/article/details/83547886