【LOJ6077】「2017 山东一轮集训 Day7」逆序对 生成函数+组合数+DP

时间:2023-03-09 16:01:07
【LOJ6077】「2017 山东一轮集训 Day7」逆序对 生成函数+组合数+DP

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