luogu P3270 [JLOI2016]成绩比较

这题我好像理解了几天/kk。

分三步考虑:

第一部分计数

由于每个人都是不一样的,所以我们首先要枚举哪 \(k\) 个人被碾压,贡献是一个组合数 \(\binom {n-1} k\)(B神自己不算在内)。

第二部分计数

现在我们要考虑对于每一门学科,哪些人比B神高,哪些人考的成绩小于等于B神。设恰有 \(i\) 个人没有被B神碾压的方案数为 \(f_i\),至多有 \(i\) 个人被B神碾压的方案数为 \(g_i\)。答案就是 \(f_{n-k-1}\)。我们发现:

\[g_i=\sum\limits_{j=0}^i \binom i j f_j \]

根据二项式反演可得:

\[f_i=\sum\limits_{j=0}^i (-1)^{i-j}\binom i j g_j \]

然后这个 \(g_i\) 其实也非常好算:

\[g_i=\prod\limits_{i=1}^m \binom {n-k-1}{r_i-1} \]

此部分时间复杂度 \(O(n^2)\)

第三部分计数

现在对于每一门课,我们知道了每个人与B神相比的状态,只要再枚举分数就好了。设 \(M_i\) 表示第 \(i\) 门课的方案数(最后用乘法原理乘起来),暴力枚举B神的分数:

\[\begin{aligned} M_i&=\sum\limits_{i=1}^{U_i}(U_i-i)^{r_i-1}\times i^{n-r_i}\\ &=\sum\limits_{i=1}^{U_i} \sum\limits_{j=1}^{r_i-1}\binom {r_i-1} jU_i^j\times (-1)^{r_i-j-1}\times i^{n-j-1}\\ &=\sum\limits_{j=1}^{r_i-1}\binom{r_i-1}{j}U_i^j\times (-1)^{r_i-j-1}\sum\limits_{i=1}^{U_i} i^{n-j-1} \end{aligned} \]

发现后面那个东东是自然数幂和,所以可以拉格朗日插值直接爆算,时间复杂度 \(O(n^3)\)

最后把三个部分乘起来就行了qwq。

代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#define int long long
 
using namespace std;

const int N=1009,M=1000000007;
int n,m,k,c[N][N],U[N],R[N],pre[N],suf[N],fac[N],inv_fac[N],d[N],cnt,a[N],p[N];

int ksm(int a,int b)
{
	int res=1;
	while(b)
	{
		if(b&1)
			res=res*a%M;
		b>>=1,a=a*a%M;
	}
	return res;
}

void init()
{
	scanf("%lld %lld %lld",&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]);
	for (int i=0;i<=100;i++)
		c[i][0]=1;
	for (int i=1;i<=100;i++)
		for (int j=1;j<=i;j++)
			c[i][j]=(c[i-1][j]+c[i-1][j-1])%M;
}

void prework(int k)
{
	d[1]=1;cnt=0;
	for (int i=2;i<=k+5;i++)
		a[i]=0;
	for (int i=2;i<=k+5;i++)
	{
		if(!a[i])
			a[i]=i,p[++cnt]=a[i],d[i]=ksm(i,k);
		for (int j=1;j<=cnt;j++)
		{
			if(p[j]>a[i]||p[j]>(k+5)/i)
				break;
			a[p[j]*i]=p[j],d[p[j]*i]=d[p[j]]*d[i]%M;
		}
	}
	for (int i=2;i<=k+5;i++)
		d[i]=(d[i]+d[i-1])%M;
}

int calc(int x,int k)
{
	prework(k-1);
	pre[0]=suf[k+2]=1;
	for (int i=1;i<=k+1;i++)
		pre[i]=pre[i-1]*(x-i)%M;
	for (int i=k+1;i>=1;i--)
		suf[i]=suf[i+1]*(x-i)%M;
	fac[0]=1;
	for (int i=1;i<=k+1;i++)
		fac[i]=fac[i-1]*i%M;
	inv_fac[k+1]=ksm(fac[k+1],M-2);
	for (int i=k;i>=0;i--)
		inv_fac[i]=inv_fac[i+1]*(i+1)%M;
	int ans=0;
	for (int i=1;i<=k+1;i++)
		ans=(ans+d[i]*pre[i-1]%M*suf[i+1]%M*inv_fac[i-1]%M*inv_fac[k+1-i]%M*((k+1-i&1)?-1:1))%M;
	return ans;
}

void work()
{
	int ans1=0,ans2=1,d=n-k-1;
	for (int i=d;i>=0;i--)
	{
		int tmp=((i-d)&1)?-1:1;
		for (int j=1;j<=m;j++)
			tmp=tmp*c[i][R[j]-1]%M;
		ans1=(ans1+tmp*c[d][i]%M)%M;
	}
	for (int i=1;i<=m;i++)
	{
		int tmp=0;
		for (int k=0;k<=R[i]-1;k++)
			tmp=(tmp+ksm(U[i],k)*calc(U[i],n-k)%M*c[R[i]-1][k]%M*((R[i]-1-k&1)?-1:1))%M;
		ans2=ans2*tmp%M;
	}
	printf("%lld\n",(c[n-1][k]*ans1%M*ans2%M+M)%M);
}

signed main()
{
	init();
	work();
	return 0;
}

posted @ 2020-08-18 00:34  With_penguin  阅读(91)  评论(0编辑  收藏  举报