【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神前面)
所以,我们根据乘法原理,先算出:
但是,你会发现,我们求出来的这个东西代表的是至多有n-1-k个人不被碾压的方案数。
举个例子:
假设此时没有被碾压的有3个人,m=2,r[1]=2,r[2]=3,记3个人的标号为1,2,3。
那么我们显然会统计到
第一门课1比B神高;
第二门课1,2比B神高这种情况。
但是,你会发现:3此时会被B神碾压。
所以,我们要容斥。
令
那我们这部分答案就是\(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人):
显然,u非常大,我们没办法暴力枚举。
但同时,你会发现,n很小,最多这有n种不同的成绩。
那么,我们把i从1枚举到n,算出不同成绩数为i的方案数,最后加法原理加一下就好了。
令
也就是:
但是,我们枚举i算出的并不是不同成绩数为i的方案数,而是不同成绩数在[1,i]之间的方案总数。(不懂得手动模拟一下)
那就容斥呗。
我们令\(D(i)\)表示不同成绩数仅为i的方案数,那么就有:
那么这部分答案就是:\(\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;
}