【Hackerrank 101Hack 43】【JZOJ5135】K-Inversion Permutations 题解

题目大意

  求长度为 $n$、逆序对数量为 $k$ 的排列的数量。
  $n, k \le 10^5$

【50%】n, k<=1000

  设 $f[i][j]$ 表示长度为 $i$、逆序对数量为 $j$ 的方案数。
  $f[i][j]=\sum_{now=0}^{i-1} f[i-1][j-now]$

【100%解法1】

  考虑每个元素的贡献(该元素前面有多少个比它大),可以得到一个贡献序列 $B$。其中该序列满足 $0<=B[i]<=i-1$,且 $\sum B[i]=k$。
  该序列与原序列唯一对应,所以我们要求有多少不同的 $B$ 序列。

  考虑 $B[i]$ 的生成函数: $1+x+x^2+…+x^{i-1}=\frac{1-x^i}{1-x}$,因此最后和为 $k$ 的方案数就是 $\prod_{i=1}^n \frac{1-x^i}{1-x}$ 的 $x^k$ 的系数。

  来一个不清真的做法。

  设 $F(x)=\prod_{i=1}^n \frac{1-x^i}{1-x}$,则

  用调和级数那样 $O(k~log~k)$ 处理 $ln~F(x)$,FFT 算 $e^{ln~F(x)}$

  以上抄自 WerKeyTom_FTD 给的笔记,我还没学会

【100%解法2】

  来一个清真的做法。

  所以现在分成前面和后面两个部分,最后枚举前面是 $x^i$,后面就是 $x^{k-i}$,把对应系数乘起来即可。后面的系数是普通组合数,所以目标变成算前面的系数。

  前面部分可以看成:有 $n$ 个物品,第 $i$ 个物品大小为 $i$,且选了 $x$ 个物品的话方案数要乘上 $(-1)^x$,求大小为 $k$ 的方案数。

  这就是个经典 dp。设 $f[i][j]$ 表示用 $i$ 个互不相同的物品、和为 $j$ 的方案数。我们限定这 $i$ 个物品是有序(从小到大)的。
  转移有两种操作:一种是全体体积加 $1$,一种是新加一个大小为 $1$ 的物品。(注意新加一个物品的时候,$-1$ 的指数会变,所以这时候算方案数是 $f[i][j]-=…$。)
  这样垒上去可能会使某些物品大小超过 $n$,所以当某个物品超限的时候要及时减去,即 $f[i][j]+=f[i-1][j-(n+1)]$。(注意这里 $-1$ 的指数又变了,所以是加等于。)

  然后会发现物品最多只需要 $\sqrt k$ 个,所以复杂度是 $O(k \sqrt k)$

代码

//解法2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#include<cmath>
#include<cstdio>
#include<algorithm>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
using namespace std;

typedef long long LL;

const int maxn=1e5+5, maxsqrtk=448;
const LL mo=1e9+7;

int n,k;

LL fac[2*maxn],ny[2*maxn];
LL mi(LL x,LL y)
{
LL re=1;
for(; y; y>>=1, x=x*x%mo) if (y&1) re=re*x%mo;
return re;
}
void C_Pre(int n)
{
fac[0]=ny[0]=1;
fo(i,1,n) fac[i]=fac[i-1]*i%mo;
ny[n]=mi(fac[n],mo-2);
fd(i,n-1,1) ny[i]=ny[i+1]*(i+1)%mo;
}
LL C(int n,int m) {return fac[n]*ny[m]%mo*ny[n-m]%mo;}

LL f[maxsqrtk+2][maxn],g[maxn];
int main()
{
scanf("%d %d",&n,&k);

C_Pre(2*max(n,k));

f[0][0]=g[0]=1;
fo(i,1,maxsqrtk)
fo(j,1,k)
{
if (j>=i) (f[i][j]-=f[i-1][j-i])%=mo; // 新加一个大小为1的物品
if (j>=i) (f[i][j]+=f[i][j-i])%=mo; // 全体+1
if (j>=n+1) (f[i][j]+=f[i-1][j-(n+1)])%=mo;
(g[j]+=f[i][j])%=mo;
}

LL ans=0;
fo(i,0,k) (ans+=g[i]*C(k-i+n-1,n-1))%=mo;

printf("%lld\n",(ans+mo)%mo);
}