bzoj 4559: [JLoi2016]成绩比较 dp+拉格朗日插值

24 篇文章 0 订阅
2 篇文章 0 订阅

题意

G系共有n位同学,M门必修课。这N位同学的编号为0到N-1的整数,其中B神的编号为0号。这M门必修课编号为0到M-1的整数。一位同学在必修课上可以获得的分数是1到Ui中的一个整数。如果在每门课上A获得的成绩均小于等于B获得的成绩,则称A被B碾压。在B神的说法中,G系共有K位同学被他碾压(不包括他自己),而其他N-K-1位同学则没有被他碾压。D神查到了B神每门必修课的排名。这里的排名是指:如果B神某门课的排名为R,则表示有且仅有R-1位同学这门课的分数大于B神的分数,有且仅有N-R位同学这门课的分数小于等于B神(不包括他自己)。我们需要求出全系所有同学每门必修课得分的情况数,使其既能满足B神的说法,也能符合D神查到的排名。这里两种情况不同当且仅当有任意一位同学在任意一门课上获得的分数不同。你不需要像D神那么厉害,你只需要计算出情况数模10^9+7的余数就可以了。
N<=100,M<=100,Ui<=10^9

分析

这道题条件很多,搞得我们很烦
我们可以先一样样考虑,一门课i,最高分为Ui,现在B神排名为Ri个人,就有

di=j=1UijnRi(Uij)Ri1 d i = ∑ j = 1 U i j n − R i ( U i − j ) R i − 1

就是枚举B神的分数,然后排名靠前的人分数大,否则的分数小于等于

然后现在要考虑哪些人比B神高,我们可以用个dp

f[i][j] f [ i ] [ j ]
表示前i门课,被B神碾压的有j人
考虑转移:
f[i][j]=k=jnf[i1][k](kj)(nkRi1(kj))di f [ i ] [ j ] = ∑ k = j n f [ i − 1 ] [ k ] ( k j ) ( n − k R i − 1 − ( k − j ) ) d i

意思就是之前有k个人被碾压,你现在选出j个人继续被碾压,剩下的k-j个人这一门成绩一定比B神高,所有在剩下n-k个之前没有被碾压的人里面,再挑选出Ri-1-(k-j)在这一门成绩比B神高

考虑Ui很大,第一个式子我们要想办法求
对于第一个式子,我们可以Ui看成自变量x,由于累加的关系,我们要把最高次项变成n(其实这里不知道最高次项也没有关系,往大的试就好,但是这一定是一个多项式)

所以我们可以通过对Ui进行拉格朗日插值,然后用dp求解,时间复杂度为

O(N3) O ( N 3 )

代码

#include <bits/stdc++.h>
#define ll long long
#define c(i,j) (fac[(i)] * inv[(j)] % mod * inv[(i) - (j)] % mod)

using namespace std;

const ll N = 110;
const ll mod = 1e9+7;

inline ll read()
{
  ll p=0; ll f=1; char ch=getchar();
  while(ch<'0' || ch>'9'){if(ch=='-') f=-1; ch=getchar();}
  while(ch>='0' && ch<='9'){p=p*10+ch-'0'; ch=getchar();}
  return p*f;
}


ll qpow(ll x,ll k,ll mo){ll s=1; while(k){if(k&1) s=s*x%mo; x=x*x%mo; k>>=1;} return s;}

ll r[N],u[N],d[N];

ll x[N],y[N],l[N],L[N],n;

void cz(ll id)
{
  for(ll i=1;i<=n+1;i++)
  {
    // printf("%lld .. \n",qpow(i,n-r[id],mod) * qpow(u[id]-i,r[id]-1,mod));
    y[i] =(y[i-1] + qpow(i,n-r[id],mod) * qpow(u[id]-i,r[id]-1,mod) % mod + mod) % mod;
  }

  for(ll k=1;k<=n+1;k++)
  {
    l[k] = 1;
    for(ll j=1;j<=n+1;j++) if(j!=k)
      l[k] = l[k] * (u[id]-j) % mod * qpow(k-j,mod-2,mod) % mod;
    d[id] =(d[id] + l[k] * y[k] % mod) % mod;
  }d[id] = (d[id] + mod) % mod; // printf("%lld\n",d[id]);
}

ll fac[N],inv[N]; ll m,k;

ll f[N][N];

int main()
{

  n = read(); m = read(); k = read();
  for(ll i=1;i<=m;i++) u[i] = read();
  for(ll i=1;i<=m;i++) r[i] = read();
  for(ll i=1;i<=m;i++)
    cz(i);

  fac[0] = 1; for(ll i=1;i<=n;i++) fac[i] = fac[i-1] * i % mod;
  inv[0] = inv[1] = 1; for(ll i=2;i<=n;i++) inv[i] = (mod - mod/i) * inv[mod % i] % mod;
  for(ll i=2;i<=n;i++) inv[i] = inv[i-1] * inv[i] % mod;

  f[0][n-1] = 1;
  for(ll i=1;i<=m;i++)
    for(ll j=k;j<n;j++)
    {
      for(ll s=j;s<n;s++)
      {
        if(r[i]-1-s+j >= 0 && s>=j && n-1-s >= r[i]-1-s+j)
          f[i][j] = (f[i][j] + (f[i-1][s] * c(s,j) % mod * c(n-1-s,r[i]-1-s+j) % mod * d[i] % mod) ) % mod;
      }
      // printf("(%lld %lld) , %lld\n",i,j,f[i][j]);
    }


  return printf("%lld\n",(f[m][k] + mod) % mod),0;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值