「JLOI2016 / SHOI2016」成绩比较

传送门


problem

在这里插入图片描述
数据范围: n ≤ 100 n\le100 n100 m ≤ 100 m\le100 m100 u i ≤ 1 0 9 u_i\le10^9 ui109


solution

我总是想不到这种计数类问题。。。

f [ i ] [ j ] f[i][j] f[i][j] 表示在前 i i i 门课,被B神碾压的同学数为 j j j 的方案数,那么有转移方程:

f [ i ] [ j ] = ∑ k = j n f [ i − 1 ] [ k ] ( k k − j ) ( n − k − 1 r i − 1 − ( k − j ) ) d i f[i][j]=\sum_{k=j}^nf[i-1][k]\binom k {k-j}\binom {n-k-1}{r_i-1-(k-j)}d_i f[i][j]=k=jnf[i1][k](kjk)(ri1(kj)nk1)di

其中 d i d_i di 表示在第 i i i 门课上分配成绩的方案数,我们先不管它。

这个转移的意思就是,由于被碾压的人数从 k k k 减为了 j j j,说明就要从这 k k k 个人中选出 ( k − j ) (k-j) (kj) 个人成绩比B神高;又由于本身有 ( r i − 1 ) (r_i-1) (ri1) 个人成绩比B神高,所以要从剩下的 ( n − k − 1 ) (n-k-1) (nk1) 个人中选 r i − 1 − ( k − j ) r_i-1-(k-j) ri1(kj) 个。

现在考虑计算 d i d_i di,我们可以直接枚举B神的分数,也就是:

d i = ∑ j = 1 u i j n − r i ( u i − j ) r i − 1 d_i=\sum_{j=1}^{u_i}j^{n-r_i}(u_i-j)^{r_i-1} di=j=1uijnri(uij)ri1

但是 u i u_i ui 1 0 9 10^9 109 级别的,直接枚举显然不可行。其实可以把 d i d_i di 看做是关于 u i u_i ui 的多项式,然么根据自然数幂和的结论,这个多项式的次数是不超过 r i r_i ri 的(也就是不超过 n n n 的),我们可以暴力算出 ( n + 1 ) (n+1) (n+1) 个点值然后拉格朗日插值即可。

时间复杂度 O ( n 3 ) O(n^3) O(n3)


code

#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 105
#define P 1000000007
using namespace std;
int n,m,K;
int u[N],r[N],y[N],c[N][N],f[N][N],Pow[N][N];
int add(int x,int y)  {return x+y>=P?x+y-P:x+y;}
int dec(int x,int y)  {return x-y< 0?x-y+P:x-y;}
int mul(int x,int y)  {return 1ll*x*y%P;}
int power(int a,int b,int ans=1){
	for(;b;b>>=1,a=mul(a,a))
		if(b&1)  ans=mul(ans,a);
	return ans;
}
void prework(){
	for(int i=0;i<N;++i){
		c[i][0]=c[i][i]=1;
		for(int j=1;j<i;++j)  c[i][j]=add(c[i-1][j-1],c[i-1][j]);
	}
	Pow[0][0]=1;
	for(int i=1;i<N;++i){
		Pow[i][0]=1,Pow[i][1]=i;
		for(int j=2;j<N;++j)  Pow[i][j]=mul(Pow[i][j-1],i);
	}
}
int calc(int u,int r){
	memset(y,0,sizeof(y));
	for(int i=1;i<=n+1;++i)
		for(int j=1;j<=i;++j)
			y[i]=add(y[i],mul(Pow[j][n-r],Pow[i-j][r-1]));
	int ans=0;
	for(int i=1;i<=n+1;++i){
		int a=y[i],b=1;
		for(int j=1;j<=n+1;++j){
			if(i==j)  continue;
			a=mul(a,dec(u,j)),b=mul(b,dec(i,j));
		}
		ans=add(ans,mul(a,power(b,P-2)));
	}
	return ans;
}
int C(int n,int m){
	return (n<0||m<0||n<m)?0:c[n][m];
}
int main(){
	prework();
	scanf("%d%d%d",&n,&m,&K);
	for(int i=1;i<=m;++i)  scanf("%d",&u[i]);
	for(int i=1;i<=m;++i)  scanf("%d",&r[i]);
	f[0][n-1]=1;
	for(int i=1;i<=m;++i){
		int val=calc(u[i],r[i]);
		for(int j=K;j<n;++j)
			for(int k=j;k<n;++k)
				f[i][j]=add(f[i][j],mul(mul(val,f[i-1][k]),mul(C(k,k-j),C(n-k-1,r[i]-1-(k-j)))));
	}
	printf("%d\n",f[m][K]);
	return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值