给定 n,k ,请求出长度为 n 的逆序对数恰好为 k 的排列的个数。
答案对 109+7 取模。
1≤n,k≤105,1≤k≤(n2)
首先问题可以转化成,你有 n 个变量 ai , ai 的取值范围是 [0,i−1] 。
你要计算出使得 ∑ni=1ai=k 成立的取值方案。
这个怎么计算呢?有下面两种方法,不过其实殊途同归。
考虑使用容斥,我们限制一些 ai≥i 。
设我们限制的 ai≥i 的 i 之和为 s ,根据挡板原理,方案数就是 (k−s+n−1n−1) 。如果我们限制了 m 个 ai ,那么我们就要乘上容斥系数 (−1)m 。
虽然总的方案有 2n 种,但是实际上,如果我限制的 ai 的 i 之和超过了 k ,它对答案是没有贡献的。也就是我只需要计算:在 1 至 n 中选出若干个数,使其和为 1 至 k 然后限制个数为特定奇偶性的方案数。
题目实际上是求
于是现在的问题就变成了怎么计算在 1 至 n 中选出若干个数,使其和为 1 至 k 然后限制个数为特定奇偶性的方案数。这其实是一个经典问题。
脑补一下这样一个构造过程:一开始什么数都没有。对已有的数,我们有两种操作,一种是全部加 1 ,一种是全部加 1 然后再加入一个值为 1 的数。这样执行若干次操作之后构造出来的数列一定满足条件。
于是我们就可以写出这样的方程:设 fi,j 表示已经选择了 i 个数,目前的和为 j 的方案,有如下几种转移:
#include <iostream>
#include <cstdio>
#include <cmath>
using namespace std;
const int P=1000000007;
const int N=100050;
const int L=N<<1;
const int M=448;
int fact[L],invf[L];
int n,K,l,m,ans;
int f[M][N];
int quick_power(int x,int y)
{
int ret=1;
for (;y;y>>=1,x=1ll*x*x%P) if (y&1) ret=1ll*ret*x%P;
return ret;
}
void pre()
{
fact[0]=1;
for (int i=1;i<=l;++i) fact[i]=1ll*fact[i-1]*i%P;
invf[l]=quick_power(fact[l],P-2);
for (int i=l;i>=1;--i) invf[i-1]=1ll*invf[i]*i%P;
}
int C(int n,int m){return 1ll*fact[n]*invf[m]%P*invf[n-m]%P;}
void dp()
{
f[0][0]=1;
for (int i=1;i<=m;++i)
for (int j=0;j<=K;++j)
{
if (j>=i) f[i][j]=(f[i][j-i]+f[i-1][j-i])%P;
if (j>=n+1) f[i][j]=(f[i][j]-f[i-1][j-(n+1)]+P)%P;
}
}
void calc()
{
ans=0;
for (int i=0;i<=m;++i)
for (int k=0;k<=K;++k)
(ans+=(((i&1)?-1ll:1ll)*f[i][k]*C(K-k+n-1,n-1)%P+P)%P)%=P;
}
int main()
{
freopen("inverse.in","r",stdin),freopen("inverse.out","w",stdout);
scanf("%d%d",&n,&K),l=n+K,m=trunc(sqrt(K*2-1)),pre(),dp(),calc(),printf("%d\n",ans);
fclose(stdin),fclose(stdout);
return 0;
}