【LOJ6077】「2017 山东一轮集训 Day7」逆序对
题目描述
给定 n,k ,请求出长度为 n的逆序对数恰好为 k 的排列的个数。答案对 109+7 取模。
对于一个长度为 n 的排列 p ,其逆序对数即满足 i<j 且 pi>pj 的二元组 (i,j)的数量。
输入格式
一行两个整数 n,k。
输出格式
一行,表示答案。
样例输入
7 12
样例输出
531
数据范围与提示
对于 20% 的数据,n,k≤20;
对于 40% 的数据,n,k≤100;
对于 60% 的数据,n,k≤5000;
对于 100% 的数据,$1 \leq n, k \leq 100000, 1 \leq k \leq \binom{n}{2}$。
题解:本人第一思路是生成函数,但是想了想模数1e9+7没法搞,后来发现这个思路还真的是对的。(还真的有人拿生成函数A了,太神了)
首先从小到大插入第i个数时,逆序对数可能增加0,1,2,...i-1,所以最终得到的生成函数就是
$f(n)=1\times(1+x)\times(1+x+x^2)\times(1+x+x^2+x^3)...$
$f(n)={\prod\limits_{i=1}^n(1-x^i)\over(1-x)^n}$
下面那个东西很好求,${1\over (1-x)^n}=(1+x+x^2+...)^n=\sum C_{i+n-1}^{n-1}x^i$,然后我们考虑上面那个东西有什么意义。
你可以理解为第i项的系数是:有n个数,1,2,3...n,从中选出j个数使得总和为i的方案数$\times(-1)^j$。
这就大大简化了我们的问题,我们令f[j][i]表示选出j个数总和为i的方案数,显然j是$\sqrt{i}$级别的。
但是我们选出来的j个数并不能重复,所以这个问题还是比较难处理的,我们可以再转化一下,求长度为j,每个数在[1,n]之间,总和为i的上升序列的方案数。
如何构造出所有的上升序列呢?我们考虑将这个序列逆向差分$(b_i=a_i-a_{i+1})$,于是这个序列的总和就变成了$\sum\limits_{k=1}^jb_k\times k$。我们只需要满足$b_k>0$即可。
这时就容易DP了,f[i][j]可以由这几种状态转移而来:
如果i>=j,我们可以将bj++,那么f[j][i]+=f[j][i-j];我们还可以在bj后面增加一个1,那么f[j][i]+=f[j-1][i-j]。
如果i>n,此时可能出现a序列的最后一项>n的情况,即b序列的总和>n的情况,那么f[j][i]-=f[j-1][i-n-1]即可。
最后统计一下答案即可,时间复杂度$O(k\sqrt{k})$
#include <cstdio> #include <iostream> #include <cstring> using namespace std; const int N=100010; const int M=450; const int P=1000000007; typedef long long ll; ll jc[N<<1],jcc[N<<1],ine[N<<1]; ll ans; int f[M][N]; int n,k; inline ll c(int a,int b) { if(a<b) return 0; return jc[a]*jcc[b]%P*jcc[a-b]%P; } inline void upd(int &x,int y) { x+=y; if(x>=P) x-=P; } int main() { scanf("%d%d",&n,&k); int i,j; ine[0]=ine[1]=jc[0]=jc[1]=jcc[0]=jcc[1]=1; for(i=2;i<=n+k;i++) jc[i]=jc[i-1]*i%P,ine[i]=P-(P/i)*ine[P%i]%P,jcc[i]=jcc[i-1]*ine[i]%P; f[0][0]=1; for(i=1;i<M;i++) { for(j=i;j<=k;j++) { if(j>=i) upd(f[i][j],f[i][j-i]),upd(f[i][j],f[i-1][j-i]); if(j>n) upd(f[i][j],P-f[i-1][j-n-1]); } } for(i=0;i<=k;i++) { ll tmp=0; for(j=0;j<M;j++) tmp+=((j&1)?-1:1)*f[j][i]; tmp=(tmp%P+P)%P; ans=(ans+tmp*c(k-i+n-1,n-1))%P; } printf("%lld",ans); return 0; }