LOJ #2026「JLOI / SHOI2016」成績比較
很好的鍛鍊推柿子能力的題目
題意
有$n$個人$ m$門學科,第$ i$門的分數為不大於$U_i$的一個正整數
定義A「打爆」B當且僅當A的每門學科的分數都不低於B的該門學科的分數
已知第一個人第$ i$們學科的排名為$ R_i$,
即這門學科不低於$ n-R_i$人的分數,但一定低於$ R_i-1$人的分數
求有多少種方案使得第一個人恰好「打爆」了$ k$個人
兩種方案不同當且僅當存在兩個人的分數不同
$ n,m \leq 100 ,U_i \leq 10^9$
$ Solution$
首先容斥
設$ g_x$表示第一個人至少「打爆」了$ x$個人的方案數,
$ A_i$表示給所有人第$ i$門學科分配分數使得第一個人排名正確的方案數
有
$ g_x=\binom{n-1}{x} \prod\limits_{i=1}^m \binom{n-x-1}{n-x-R_i}A_i$
$A_i=\sum\limits_{j=1}^{U_i}j^{n-R_i}(U_i-j)^{R_i-1}$
$ g_x$的意義是
先選出被吊打的$ x$個人
再列舉每一門學科,這門學科比$ n-R_i$人高,
除去被吊打的$ x$人外還需要在未被吊打的$ n-x-1$人中選出$ n-R_i-x$人這門比第一個人低
然後再給這$ n$個人分配分數
$ A_i$的意義是:
列舉第一個人第$ i$門的分數$ j$,有$ n-R_i$人分數不能高於$j$,其餘$ R_i-1$人分數必須高於$ j$
容易發現瓶頸在快速計算$ A_i$上
我們將$ A_i$二項式展開得
$A_i=\sum\limits_{j=1}^{U_i}j^{n-R_i}(U_i-j)^{R_i-1}$
$A_i=\sum\limits_{j=1}^{U_i}j^{n-R_i}\sum\limits_{k=0}^{R_i-1} \binom{R_i-1}{k}{(U_i)}^k(-j)^{R_i-k-1}$
$A_i=\sum\limits_{k=0}^{R_i-1} \binom{R_i-1}{k}{(U_i)}^k\sum\limits_{j=1}^{U_i}j^{n-R_i}(-j)^{R_i-k-1}$
$A_i=\sum\limits_{k=0}^{R_i-1} \binom{R_i-1}{k}{(U_i)}^k(-1)^{R_i-k-1}\sum\limits_{j=1}^{U_i}j^{n-k-1}$
前半部分非常好算
後半部分是一個自然數冪和,可以拉格朗日插值解決
拉格朗日插值過程中可以通過預處理前後綴的方式去掉不必要的求逆元複雜度使除預處理外單次$ O(n)$
這樣就可以快速算出$ g_x$了
然後就是喜聞樂見的反演環節
設$ f_x$表示第一個人恰好「打爆」了$ x$個人
有
$ g_x=\sum\limits_{i=x}^n \binom{i}{x}f_i$
$ f_x=\sum\limits_{i=x}^n(-1)^{i-x} \binom{i}{x}g_i$
然後這道題就解決了
總複雜度:$ O(n^2m)$
$ my \ code$
#include<ctime> #include<cmath> #include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #include<queue> #define p 1000000007 #define rt register int #define ll long long using namespace std; inline ll read(){ ll x = 0; char zf = 1; char ch = getchar(); while (ch != '-' && !isdigit(ch)) ch = getchar(); if (ch == '-') zf = -1, ch = getchar(); while (isdigit(ch)) x = x * 10 + ch - '0', ch = getchar(); return x * zf; } void write(ll y){if(y<0)putchar('-'),y=-y;if(y>9)write(y/10);putchar(y%10+48);} void writeln(const ll y){write(y);putchar('\n');} int i,j,k,m,n,x,y,z,cnt; int C[2010][2010],f[105],B[105],U[105],R[105]; int ksm(int x,int y){ int ans=1; for(rt i=y;i;i>>=1,x=1ll*x*x%p)if(i&1)ans=1ll*ans*x%p; return ans; } int mi[110][100];//前i個數的j次冪 int jc[105],njc[105],inv[105]; int qz[105],hz[105]; int cz(int n,int k){ if(!k)return n;int ans=0; if(n<=102)return mi[n][k]; int up=1;qz[0]=hz[k+3]=1; for(rt i=1;i<=k+2;i++)qz[i]=1ll*qz[i-1]*(n-i)%p; for(rt i=k+2;i>=1;i--)hz[i]=1ll*hz[i+1]*(n-i)%p; for(rt i=1;i<=k+2;i++){ int down=1ll*njc[i-1]*njc[k+2-i]%p; if(i+k&1)down=-down; (ans+=1ll*mi[i][k]*qz[i-1]%p*hz[i+1]%p*down%p%p)%=p; } return ans; } int main(){ jc[0]=jc[1]=njc[0]=njc[1]=inv[0]=inv[1]=1; for(rt i=2;i<=100;i++){ jc[i]=1ll*jc[i-1]*i%p; inv[i]=1ll*inv[p%i]*(p-p/i)%p; njc[i]=1ll*njc[i-1]*inv[i]%p; } for(rt i=1;i<=102;i++) for(rt j=1;j<=100;j++)if(j==1)mi[i][j]=i;else mi[i][j]=1ll*mi[i][j-1]*i%p; for(rt j=1;j<=100;j++) for(rt i=2;i<=102;i++)mi[i][j]=(mi[i-1][j]+mi[i][j])%p; n=read();m=read();int K=read(); for(rt i=1;i<=m;i++)U[i]=read(); for(rt i=1;i<=m;i++)R[i]=read(); for(rt i=0;i<=100;i++){ C[i][0]=1; for(rt j=1;j<=i;j++) C[i][j]=(C[i-1][j]+C[i-1][j-1])%p; } for(rt i=1;i<=m;i++){ for(rt k=0;k<=R[i]-1;k++){ int ret=0; (ret+=1ll*C[R[i]-1][k]*ksm(U[i],k)%p*cz(U[i],n-k-1)%p)%=p; if(R[i]-k-1&1)(B[i]-=ret)%=p;else (B[i]+=ret)%=p; } } for(rt i=1;i<n;i++){ f[i]=1; for(rt j=1;j<=m;j++){ if(n-i-R[j]<0){ f[i]=0; break; } (f[i]=1ll*f[i]*C[n-i-1][n-i-R[j]]%p*B[j]%p)%=p; } f[i]=1ll*f[i]*C[n-1][i]%p; } int ans=0; for(rt j=K,tag=1;j<n;j++,tag*=-1)(ans+=1ll*f[j]*C[j][K]*tag%p)%=p; cout<<(ans+p)%p; return 0; }