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

题目大意

       求长度为 n 、逆序对数量为 k 的排列的数量。
      n,k<=1e5


【50%】n, k<=1000

       f[i][j] 表示长度为 i 、逆序对数量为 j 的方案数。
      f[i][j]=i1now=0f[i1][jnow]

【100%解法1】

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

       考虑 B[i] 的生成函数: 1+x+x2+...+xi1=1xi1x ,因此最后和为 k 的方案数就是 ni=11xi1x xk 的系数。

       来一个不清真的做法。

       F(x)=ni=11xi1x ,则 ln F(x)=ni=1ln(1xi)n ln(1x)
      ln(1x)=(x+x22+x33+...)
      ln(1xi)=(xi+x2i2+x3i3+...)
       用调和级数那样 O(k log k) 处理 ln F(x) ,FFT 算 eln F(x)

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

【100%解法2】

       来一个清真的做法。

i=1n1xi1x  =  ni=1(1xi)(1x)n  =  i=1n(1xi)×i=0n(i+n1n1)xi

       所以现在分成前面和后面两个部分,最后枚举前面是 xi ,后面就是 xki ,把对应系数乘起来即可。后面的系数是普通组合数,所以目标变成算前面的系数。

       前面部分可以看成:有 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[i1][j(n+1)] 。(注意这里 1 的指数又变了,所以是加等于。)

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

代码

//解法2

#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);
}

猜你喜欢

转载自blog.csdn.net/rzo_kqp_orz/article/details/72861538