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; }