zoukankan      html  css  js  c++  java
  • 【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}^{ile 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}^{ile 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}^{ile u}i^bcdot (u-i)^a ]

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

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

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

    [G(u,a,b)=sum _{i=1}^{ile u}i^bcdot (u-i)^a ]

    也就是:

    [sum _{i=1}^{ile 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}^{ile 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;
    }
    
  • 相关阅读:
    在单向链表中删除指定的key
    双向链表反转
    单向链表反转
    认识异或运算
    二分查找
    插入排序
    冒泡排序
    选择排序
    go 语言环境安装
    欧几里得算法
  • 原文地址:https://www.cnblogs.com/SillyTieT/p/11661514.html
Copyright © 2011-2022 走看看