题解 P2159 【[SHOI2009]舞会】

(first)

看到数据范围应该都会多多少少有点思路了,要搞一个大概是(n^3)(dp),可是貌似最多的方案数是(200!)级别的,计算器一算发现是(6e600+),于是要套高精。所以之前的(n^3)排除,将注意力放在(n^2-n^2logn),可(logn)这种奇怪的复杂度怎么会无缘无故出现在一个(n<=200)的题目?于是果断考虑(n^2dp)

(n^2)都知道什么意思吧,就是设置两个状态的(dp)方程

(second)

首先应当想到先排序,因为当身高有序后才能方便找出比某一女生矮的男生有多少,以方便进行(dp)计算

(third)

考虑设置(dp)方程(f_{i,j})表示当前遍历到第(i)矮的女生,我们已经匹配了(j)对,并且(j)对全是女大于男的方案数,这样的话(dp)方程可以比较快地得到

考虑将第(i)个女生与比第(i)个女生矮而不矮于(i-1)个女生的一众男生(设人数为(p))一起加入(f_{i,j})的更新

那么(f_{i,j})首先肯定无条件继承(f_{i-1,j})的值,而当(p>j-1)时表明我们可以再已经有(j-1)对的基础上再扩展一对女大于男的舞伴,并且扩展的这一对有(p-(j-1))种方案,那么(f_{i,j})就需要加上(f_{i-1,j-1} * (p-j+1))

(forth)

但是(f_{n,j})还是不能帮我们解决问题,嗯

但已经接近尾声了,考虑设置(g_j)表示选完所有对,而其中至少有(j)对的方案数,那么显然(g_j=f_{n,j} * (n-j)!)(因为剩下(n-j)对没有要求,那么就是全排列嘛)

得到了(g_j)数组后就很容易想到容斥得到答案了(容斥系数自己可以瞎搞搞出来):

[ans=sum_{i=0}^{k}sum_{j=i}^{n}(-1)^{j-i}* C_j^i * g_j ]

(fifth)

最后一点小注,本篇题解是对该题第一篇题解的一点补充,代码实现思路几乎相同。但由于彼题解有些许瑕疵(也可能是我太蒟蒻)导致没有怎么看懂,后来仔细推敲代码并手搞式子才有了以上这些想法,担心会有和我一样的小菜鸡(应该不会吧)对于首篇的做法有迷惑,于是作此题解来造(bao)福(fu)人(she)类(hui)

(sixth)

代码(代码中的(f_{i,j})是用的滚动数组,没算(g_j)而是直接代入(ans)公式,若还有不懂欢迎私信):

#include<bits/stdc++.h>
using namespace std;

#define ll long long

inline int read()
{
	int x=0,f=1;char ch=getchar();
	for(;ch<'0'||ch>'9';ch=getchar()) f=ch=='-'?-1:1;
	for(;ch>='0'&&ch<='9';ch=getchar()) x=(x<<3)+(x<<1)+(ch^48);
	return x*f;
}

const int xx=2e2+10,xd=100;
const int digit=1e9;

int m[xx],wm[xx],n,k;

struct Big
{
	ll a[xd];
	inline void print()
	{
		for(register int i=a[0];i;--i)
		{
			if(i!=a[0])
				for(register int j=1e8;j>=10;j/=10)
					a[i]<j?putchar('0'):j=1;
			printf("%d",a[i]);
		}
		puts(""); 
	}
}zero,one,fac[xx],f[2][xx],C[xx][xx];
Big ans;
Big operator * (Big x,Big y)
{
	Big tmp=zero;tmp.a[0]=x.a[0]+y.a[0];
	for(register int i=1;i<=x.a[0];++i)
		for(register int j=1;j<=y.a[0];++j)
		{
			tmp.a[i+j-1]+=x.a[i]*y.a[j];
			if(tmp.a[i+j-1]>=digit)
			{
				tmp.a[i+j]+=tmp.a[i+j-1]/digit;
				tmp.a[i+j-1]%=digit;
			}
		}
	while(!tmp.a[tmp.a[0]]&&tmp.a[0]>1)
		--tmp.a[0];
	return tmp;
}
Big operator * (int x,Big y)
{
	Big tmp=zero;tmp.a[0]=y.a[0]+1;
	for(register int i=1;i<=y.a[0];++i)
	{
		tmp.a[i]+=x*y.a[i];
		if(tmp.a[i]>=digit)
		{
			tmp.a[i+1]+=tmp.a[i]/digit;
			tmp.a[i]%=digit;
		}
	}
	while(!tmp.a[tmp.a[0]]&&tmp.a[0]>1)
		--tmp.a[0];
	return tmp;
}
Big operator + (Big x,Big y)
{
	Big tmp=zero;tmp.a[0]=max(x.a[0],y.a[0])+1;
	for(register int i=1;i<=tmp.a[0]-1;++i)
	{
		tmp.a[i]+=(x.a[i]+y.a[i]);
		if(tmp.a[i]>=digit)
		{
			tmp.a[i+1]+=tmp.a[i]/digit;
			tmp.a[i]%=digit;
		}
	}
	while(!tmp.a[tmp.a[0]]&&tmp.a[0]>1)
		--tmp.a[0];
	return tmp;
}
Big operator - (Big x,Big y)
{
	Big tmp=zero;tmp.a[0]=x.a[0];
	for(register int i=1;i<=x.a[0];++i)
	{
		tmp.a[i]=x.a[i]-y.a[i];
		if(tmp.a[i]<0)
		{
			tmp.a[i]+=digit;
			--x.a[i+1];
		}
	}
	while(!tmp.a[tmp.a[0]]&&tmp.a[0]>1)
		--tmp.a[0];
	return tmp;
}

inline void pre()
{
	memset(zero.a,0,sizeof(zero.a));
	zero.a[0]=one.a[0]=one.a[1]=1;
	for(register int i=0;i<=n;++i)
		C[i][0]=one;
	for(register int i=1;i<=n;++i)
		for(register int j=1;j<=n;++j)
			C[i][j]=C[i-1][j]+C[i-1][j-1];
	for(register int i=0;i<=1;++i)
		for(register int j=1;j<=n;++j)
			f[i][j]=zero;
	fac[0]=one;
	for(register int i=1;i<=n;++i)
		fac[i]=i*fac[i-1];
	f[0][0]=f[1][0]=one;
}

int main()
{
	n=read(),k=read(),pre();
	for(register int i=1;i<=n;++i) m[i]=read();
	for(register int i=1;i<=n;++i) wm[i]=read();
	sort(m+1,m+n+1),sort(wm+1,wm+n+1);
	for(register int i=1,K=0;i<=n;++i)
	{
		while(K<n&&m[K+1]<wm[i])
			++K;
		for(register int j=1;j<=i;++j)
		{
			if(K>j-1)
				f[i&1][j]=(K-j+1)*f[(i-1)&1][j-1];
			f[i&1][j]=f[i&1][j]+f[(i-1)&1][j];
		}
	}
	ans=zero;
	for(register int i=0;i<=k;++i)
		for(register int j=i;j<=n;j+=2)
			ans=ans+(f[n&1][j]*fac[n-j]*C[j][i]);
	for(register int i=0;i<=k;++i)
		for(register int j=i+1;j<=n;j+=2)
			ans=ans-(f[n&1][j]*fac[n-j]*C[j][i]);
	ans.print();
	return 0;
}
原文地址:https://www.cnblogs.com/ALANALLEN21LOVE28/p/12635821.html