【JLOI】2016 成绩比较

容斥+计数

一开始还以为是什么玄学dp,没想到居然是一道计数题

我写法是参照这篇题解的。

分3部分讨论:

1.从n-1个人中选k个被碾压的

2.对于未被碾压的n-1-k人,求出其有多少种成绩分布情况(这里我们不关心这些人成绩的具体值,只关心其与B神的大小关系

3.求出一种合法的成绩分布情况可以有多少种分配成绩的方案数

显然,总答案就是把这三部分答案相乘。


part1.从n-1个人中选k个被碾压的

方法:无

显然,答案就是\(C(n-1,k)\)


part2.对于未被碾压的n-1-k人,求出其有多少种成绩分布情况

方法:计数+容斥

对于第i门功课,显然有\(r[i]-1\)个人在B神前面(题面都告诉你了),那么单看这一门功课,显然有\(C(n-1-k,r[i]-1)\)种分布情况。(从n-1-k个人挑出\(r[i]-1\)个,排到B神前面)

所以,我们根据乘法原理,先算出:

\[\prod _{i=1}^{i\le m}C(n-1-k,r[i]-1) \]

但是,你会发现,我们求出来的这个东西代表的是至多有n-1-k个人不被碾压的方案数

举个例子:

假设此时没有被碾压的有3个人,m=2,r[1]=2,r[2]=3,记3个人的标号为1,2,3。

那么我们显然会统计到

第一门课1比B神高;

第二门课1,2比B神高这种情况。

但是,你会发现:3此时会被B神碾压。

所以,我们要容斥。

\[F(x)=\prod _{i=1}^{i\le m}C(x,r[i]-1) \]

那我们这部分答案就是\(F(n-1-k)-F(n-2-k)+F(n-3-k)-F(n-4-k)+\cdots\)

复杂度:\(O(nm)\)

代码:

namespace solve2 {
	ll F(int x) {
		ll res=1;
		for(int i=1; i<=m; i++)res=res*C[x][r[i]-1]%MOD;//C是预处理出来的组合数数组
		return res;
	}
	ll work(int n) {//n代表n-k-1
		ll ans=0,pos=1;
		for(int i=n; i>=1; i--) {
			ll res=1LL*C[n][i]*F(i)%MOD;
			ans=(ans+pos*res+MOD)%MOD;
			pos*=-1;
		}
		return ans;
	}
}

part3.求出一种合法的成绩分布情况可以有多少种分配成绩的方案数

方法:计数+容斥

对于一种暴力的写法,我们可以枚举B神的成绩,那么答案就是(设此时满分为u,排名在B神之上的有a人,B神以下的有b人):

\[\sum _{i=1}^{i\le u}i^b\cdot (u-i)^a \]

显然,u非常大,我们没办法暴力枚举。

但同时,你会发现,n很小,最多这有n种不同的成绩

那么,我们把i从1枚举到n,算出不同成绩数为i的方案数,最后加法原理加一下就好了。

\[G(u,a,b)=\sum _{i=1}^{i\le u}i^b\cdot (u-i)^a \]

也就是:

\[\sum _{i=1}^{i\le n}G(i,a,b)\cdot C(u,i) \]

但是,我们枚举i算出的并不是不同成绩数为i的方案数,而是不同成绩数在[1,i]之间的方案总数。(不懂得手动模拟一下)

那就容斥呗。

我们令\(D(i)\)表示不同成绩数为i的方案数,那么就有:

\[D(i)=G(i,a,b)-\sum_{j=1}^{j<i}D(j)\cdot C(i,j) \]

那么这部分答案就是:\(\sum_{i=1}^{i\le n}D(i)\cdot C(u,i)\)

复杂度:\(O(n^2m)\)

代码:

namespace solve3 {
	ll D[1010];
	ll G(int x,int a,int b) {
		ll res=0;
		for(int i=1; i<=x; i++)res=(res+P[i][b]*P[x-i][a]%MOD)%MOD;//P[i][x]表示i^x
		return res;
	}
	ll GG(int u,int a,int b) {
		ll res=0,CC=1;
		for(int i=1; i<=n; i++) {
			CC=CC*(u-i+1)%MOD*inv[i]%MOD;//组合数可递推,inv[i]表示i的模逆元
			D[i]=G(i,a,b);
			for(int j=1; j<i; j++)D[i]=(D[i]-D[j]*C[i][j]%MOD+MOD)%MOD;
			res=(res+D[i]*CC%MOD)%MOD;
		}
		return res;
	}
	ll work(int n,int m) {
		ll res=1;
		for(int i=1; i<=m; i++)res=res*GG(u[i],r[i]-1,n-r[i])%MOD;
		return res;
	}
}

part4.预处理

没什么好说的,但有一点要强调:

\(P[0][0]=1\)我就是这里没加调了超级久

void init() {
	C[0][0]=1;
	for(int i=1; i<=100; i++) {
		C[i][0]=1;
		for(int j=1; j<=i; j++)C[i][j]=(C[i-1][j-1]+C[i-1][j])%MOD;
	}
	P[0][0]=1;//注意,这里不要忘了(我就是这部分调了很久)
	for(int i=1; i<=100; i++) {
		P[i][0]=1;
		for(int j=1; j<=100; j++)P[i][j]=1LL*P[i][j-1]*i%MOD;
	}
	inv[1]=1;
	for(int i=2; i<=100; i++)inv[i]=(MOD-MOD/i)*inv[MOD%i]%MOD;
}
posted @ 2019-10-12 14:45  TieT  阅读(179)  评论(0编辑  收藏  举报