[JLOI2016]成绩比较

题目描述

G 系共有 N 位同学,M 门必修课。这 N 位同学的编号为 0N1 的整数,其中 B 神的编号为 0 号。这 M 门必修课编号为 0M1 的整数。一位同学在必修课上可以获得的分数是 1Ui 中的一个整数。

如果在每门课上 A 获得的成绩均小于等于 B 获得的成绩,则称 A 被 B 碾压。在 B 神的说法中,G 系共有 K 位同学被他碾压(不包括他自己),而其他 NK1 位同学则没有被他碾压。D 神查到了 B 神每门必修课的排名。

这里的排名是指:如果 B 神某门课的排名为 R,则表示有且仅有 R1 位同学这门课的分数大于 B 神的分数,有且仅有 NR 位同学这门课的分数小于等于 B 神(不包括他自己)。

我们需要求出全系所有同学每门必修课得分的情况数,使其既能满足 B 神的说法,也能符合 D 神查到的排名。这里两种情况不同当且仅当有任意一位同学在任意一门课上获得的分数不同。

你不需要像 D 神那么厉害,你只需要计算出情况数模 109+7 的余数就可以了。
1N1001M1001Ui1091RiN

思路点拨

这篇题解是 O(n2) ,没卡常直接最优解。相比于大多数题解的 O(n3) 有绝对优势。

这道题目我们可以把答案算成三个部分。我们一个个讲:

第一部分

我们需要选出 k 个被吊打的学生,那么这个值显然是 Cnk

第二部分

我们除了B神以外的学术的相对成绩,具体的说,就是每一个学生的成绩是在B神之上还是之下。其实这一点并不麻烦,但是烦人的是,题目要求被B神吊打的学生 恰好 只有 k 个。我们按照一般的分配方式,可能会出现多于 k 个学生被 B神吊打的情况。我们很容易想到二项式反演,这样就可以将 恰好 的问题转化为 不超过 的问题。

我们可以定义 f(x) 表示至多有 x 个学生没有被吊打的情况,那么

f(x)=i=1mCxri1

x 个学生没有被吊打就意味着对于每一个学科,在 ri1 个超过B神的位置中,一定是从 x 个学生中选择的。这样是符合我们定义的。接下来,我们可以二项式反演:

ans=i=0n1k(1)n1kiCn1kif(i)

第三部分

我们需要给每一个学生分配分数。但是这个分数很大(怎么处理我们后面会讲到)
我们知道,对于每一个学科,成绩不超过B神的学生是有 nri 个的,超过B神的学生有 ri1 个。这样我们恒容易知道答案就是:

i=1mj=1Uijnri(Uij)ri1

直接求会超时,并且超的很多。但是看到这种式子我们要引起警觉。 i=1nik 一类的式子其实可以被表示为一个以 n 为自变量的 k+1 次多项式。但是这个式子比较特殊,它有两个变量。但是其中的每一个部分 jnri(Uij)ri1 都是多项式表示的,那么这个式子就可以表示为这两个多项式的卷积,这显然还是一个多项式。这是一个以 Ui 为自变量的 n 次多项式,我们确定 n+1 个点就可以拉格朗日插值求出。应为我们带入的点横坐标是连续的,所以我们的插值算法可以被优化到 O(n)

如何获取答案

乘法原理,将三个部分的答案乘起来就可以了。这样子我们的时间复杂度就是每一个部分的时间复杂度 O(n+n2+n2) 。这样就可以吊打标算了。泰裤辣!

code

#include<bits/stdc++.h>
#define int long long
using namespace std;
inline int read(){
	int x=0,f=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){
		if(ch=='-') f=-f;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9'){
		x=x*10+ch-'0';
		ch=getchar();
	}
	return x*f;
}
const int MAXN=1e2+10;
const int mod=1e9+7;
int qpow(int a,int b){
	int ans=1,base=a;
	while(b){
		if(b&1) ans=ans*base%mod;
		base=base*base%mod;
		b>>=1;
	}
	return ans;
}
namespace Cmath{
	int sum[MAXN],inv[MAXN];
	void prepare(){
		sum[0]=inv[0]=1;
		for(int i=1;i<MAXN;i++){
			sum[i]=sum[i-1]*i%mod;
			inv[i]=qpow(sum[i],mod-2);
		}
	} 
	int C(int n,int m){
		if(m>n||m<0) return 0; 
		return sum[n]*inv[m]%mod*inv[n-m]%mod;
	}
	int A(int n,int m){
		return sum[n]*inv[n-m]%mod; 
	}
}
using namespace Cmath;
namespace Lagrange{
	int pre[MAXN],suc[MAXN]; 
	int lagrange(int *x,int *y,int n,int k){
		int ans=0;
		pre[0]=suc[n+1]=1;
		for(int i=1;i<=n;i++) pre[i]=pre[i-1]*(k-x[i]+mod)%mod;
		for(int i=n;i;i--) suc[i]=suc[i+1]*(k-x[i]+mod)%mod;
		for(int i=1;i<=n;i++){
			int top=pre[i-1]*suc[i+1]%mod;
			int down=sum[x[i]-x[1]]*sum[x[n]-x[i]]%mod;
			if((n-i)&1) down=(-down+mod)%mod;
			ans=(ans+top*qpow(down,mod-2)%mod*y[i])%mod;
		}
		return ans;
	}
}
using namespace Lagrange;
int n,m,k;
int U[MAXN],r[MAXN];
int x[MAXN],y[MAXN];
int fun(int p){
	int ans=1;
	for(int i=1;i<=m;i++)
		ans=ans*C(p,r[i]-1)%mod;
	return ans;
}
int run(int id){
	for(int i=1;i<=n+1;i++){
		x[i]=i;
		y[i]=(y[i-1]+qpow(i,n-r[id])*qpow(U[id]-i,r[id]-1))%mod;
	}
	return lagrange(x,y,n+1,U[id]);
}
signed main(){
	n=read(),m=read(),k=read();
	for(int i=1;i<=m;i++) U[i]=read();
	for(int i=1;i<=m;i++) r[i]=read();
	Cmath:prepare();
	int ans=0;
	for(int i=0;i<=n-k-1;i++){
		int cnt=(qpow(-1,n-k-1-i)+mod)*C(n-k-1,i)%mod*fun(i)%mod;
		ans=(ans+cnt)%mod;
	}
	ans=ans*C(n-1,k)%mod;
	for(int i=1;i<=m;i++) ans=ans*run(i)%mod;
	cout<<ans<<endl;
	return 0;
}
posted @   Diavolo-Kuang  阅读(8)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示