loj6077. 「2017 山东一轮集训 Day7」逆序对
题目描述:
题解:
容斥+生成函数。
考虑加入的第$i$个元素对结果的贡献是$[0,i-1]$,我们可以列出生成函数。
长这样:$(1)*(1+x)*(1+x+x^2)*……*(1+x+x^2+……+x^{n-1})=\frac{\prod_{i=1}^{n}1-x^i}{(1-x)^n}$
把分母提出来:$\frac{1}{(1-x)^n} = (1+x+x^2+……)^n = \sum_{i=0}^{k} C_{i+n-1}^{n-1}$,日常小球放盒。
现在还剩$\prod_{i=1}^n 1-x^i$,可以考虑将该式理解为从$1$到$n$选$i$个数,总和为$j$,对该项系数贡献为$(-1)^i$。
由于从$1$到$n$不重复,可以发现$\sum_{i=1}^{447}>100000$,那么$i \le 447$。
那么就可以$dp$了。状态为$f[i][j]$,表示当前选了$i$个数总和为$j$,且最后一项不大于$n$的方案数。
要求选数不重复怎么办?
考虑将其构造成一个上升序列。我们用枚举差值的思想,保证前后差值大于$0$。
转移有三种:
1.将$i$个数集体+1,此时$f[i][j]+=f[i][j-i]$;
2.将$i$个数集体+1再在最前面加入一个1,此时$f[i][j]+=f[i-1][j-i]$;
3.考虑我们每次都让每个数+1,所以当$j>n$时会出现最后一项为$n+1$的情况,此时$f[i][j]-=f[i-1][j-n-1]$;
这样转移就可以了。
最后卷积卷出第$k$位的值就可以了。
代码:
#include<cstdio> #include<cstring> #include<algorithm> using namespace std; typedef long long ll; const int MOD = 1000000007; const int N = 100050; const int M = 450; int fastpow(int x,int y) { int ret = 1; while(y) { if(y&1)ret=1ll*ret*x%MOD; x=1ll*x*x%MOD;y>>=1; } return ret; } template<typename T> inline void Mod(T&x){if(x>=MOD)x-=MOD;} int n,m; int jc[N<<1],jny[N<<1],f[M][N]; void init() { jc[0] = 1; for(int i=1;i<=n+m;i++)jc[i]=1ll*jc[i-1]*i%MOD; jny[n+m] = fastpow(jc[n+m],MOD-2); for(int i=n+m;i;i--)jny[i-1]=1ll*jny[i]*i%MOD; } int C(int x,int y){return 1ll*jc[x]*jny[y]%MOD*jny[x-y]%MOD;} int main() { scanf("%d%d",&n,&m); init(); f[0][0] = 1; for(int i=1;i<M;i++)for(int j=i;j<=m;j++) { Mod(f[i][j] = f[i-1][j-i]+f[i][j-i]); if(j>n)Mod(f[i][j]+=MOD-f[i-1][j-n-1]); } int ans = 0; for(int i=0;i<=m;i++) { int tmp = 0; for(int j=0;j<M;j++) if(j&1)Mod(tmp+=MOD-f[j][i]); else Mod(tmp+=f[j][i]); Mod(ans+=1ll*tmp*C(n+m-i-1,n-1)%MOD); } printf("%d\n",ans); return 0; }