zoukankan      html  css  js  c++  java
  • 【洛谷3270】 [JLOI2016] 成绩比较(拉格朗日插值)

    点此看题面

    • (n)个人,(m)个学科,一个同学第(i)门学科的得分可以是(1sim U_i)间的任一整数。
    • 现已知一共有(k)名同学每门学科的得分都小于等于第一个同学,且第(i)门学科有(R_i)名同学的得分小于等于第一个同学。
    • 求每个同学每门学科得分可能的方案数。
    • (n,mle100,U_ile10^9)

    拉格朗日插值

    我们单纯去考虑第(i)门学科所有同学得分可能的方案数,那么就是去枚举第一个同学的得分得到:

    [sum_{j=1}^{U_i}j^{n-R_i} imes(U_i-j)^{R_i-1} ]

    比较显然,(sum)中的(j)应该是一个(n-1)次项,那么在前面加上一个(sum)之后它就应该变成了一个(n)次项。

    所以我们只要求出求和上界为(1sim n+1)时的取值,然后拉格朗日插值即可计算出求和上界为(U_i)时的取值了。

    容斥计算总贡献

    (f(x))为钦定(x)个人被第一个同学碾压时,每门学科得分小于等于第一个人的人员分配方案数,显然有:

    [f(x)=C_{n-1}^x imesprod_{i=1}^{m}C_{n-1-x}^{n-R_i-x} ]

    简单解释一下,第一个组合数就是从剩余人中选出被碾压的(x)个人的方案,而后面就是考虑每门学科小于等于第一个人的人员分配方案,要除去已经确定被碾压的(x)个人。

    然后就是容斥,得到恰好(k)个人的人员分配方案数:

    [cnt=sum_{i=k}^n(-1)^{i-k} imes C_i^k imes f(i) ]

    (实际上这里的枚举上界不应该设成(n),而是可能被碾压的人数上界(min{n-R_i}))。

    最终的答案就应该是人员分配方案数再乘上每门学科得分可能的方案数,即:

    [cnt imesprod_{i=1}^mg_i ]

    代码:(O(nmlogn))

    #include<bits/stdc++.h>
    #define Tp template<typename Ty>
    #define Ts template<typename Ty,typename... Ar>
    #define Reg register
    #define RI Reg int
    #define Con const
    #define CI Con int&
    #define I inline
    #define W while
    #define N 100
    #define X 1000000007
    using namespace std;
    int n,m,k,g[N+5],u[N+5],r[N+5],IFac[N+5],C[N+5][N+5];
    I int QP(RI x,RI y) {RI t=1;W(y) y&1&&(t=1LL*t*x%X),x=1LL*x*x%X,y>>=1;return t;}
    int y[N+5],pre[N+5],suf[N+5];I int Calc(CI x)//计算第x门学科的得分分配方案
    {
    	RI i;for(i=1;i<=min(n+1,u[x]);++i) y[i]=(1LL*QP(i,n-r[x])*QP(u[x]-i,r[x]-1)+y[i-1])%X;//求出n+1个点值
    	if(u[x]<=n+1) return y[u[x]];RI j;long long t=0;//如果u[x]很小直接返回
    	for(pre[0]=j=1;j<=n+1;++j) pre[j]=1LL*pre[j-1]*(u[x]-j)%X;//前缀积
    	for(suf[n+2]=1,j=n+1;j;--j) suf[j]=1LL*suf[j+1]*(u[x]-j)%X;//后缀积
    	for(i=1;i<=n+1;++i) t+=((n+1-i)&1?-1LL:1LL)*y[i]*pre[i-1]%X*suf[i+1]%X*IFac[i-1]%X*IFac[n+1-i]%X;//拉格朗日插值
    	return (t%X+X)%X;
    }
    int main()
    {
    	RI i,j;for(scanf("%d%d%d",&n,&m,&k),C[0][0]=i=1;i<=n;++i)
    		for(C[i][0]=j=1;j<=i;++j) C[i][j]=(C[i-1][j-1]+C[i-1][j])%X;//预处理组合数
    	for(IFac[0]=i=1;i<=n;++i) IFac[i]=1LL*IFac[i-1]*QP(i,X-2)%X;//预处理阶乘逆元
    	RI Mx=n;for(i=1;i<=m;++i) scanf("%d",u+i);for(i=1;i<=m;++i) scanf("%d",r+i),Mx=min(Mx,n-r[i]);//Mx记录可能被碾压人数上界
    	RI s=0,t;for(i=1;i<=m;++i) g[i]=Calc(i);for(i=k;i<=Mx;++i)//至少k个人
    	{
    		for(t=C[n-1][i],j=1;j<=m;++j) t=1LL*t*C[n-1-i][n-r[j]-i]%X;
    		s=(((i-k)&1?X-1LL:1LL)*C[i][k]%X*t+s)%X;//容斥
    	}
    	for(i=1;i<=m;++i) s=1LL*s*g[i]%X;return printf("%d
    ",s),0;//人员分配方案数×得分分配方案数
    }
    
    败得义无反顾,弱得一无是处
  • 相关阅读:
    数组
    循环(二)
    循环——笔记
    C#基础(四)
    C#基础(三)
    安卓帮助文档
    增加线程异步发送消息的方法二(Runnable)
    增加线程异步发送消息的方法一(Thread)
    获取下拉框的值
    获取表中字段最大值,并且保存在前台页面中
  • 原文地址:https://www.cnblogs.com/chenxiaoran666/p/Luogu3270.html
Copyright © 2011-2022 走看看