【LOJ6077】【2017 山东一轮集训 Day7】逆序对(排列计数DP,容斥,旋转体积背包)

搞不懂排列计数。

一种计数方法是考虑从小往大往当前排列中插入每个数,存在一次插的位置不同那么最后的结果也不同。

那么考虑插入 \(i\) 时,对于 \(x\in[0,i-1]\) 均有且仅有一种方法使得逆序对个数增加 \(x\)

法一:

考虑 DP,设 \(f_{i,j}\) 表示插入了 \(i\) 个数,已经有 \(j\) 个逆序对的方案数,那么有转移 \(f_{i,j}=\sum\limits_{l=0}^{i-1}f_{i-1,j-l}\)

暴力转移是 \(O(n^2)\) 的,考虑使用生成函数优化。设 \(F_i(x)\)\(f_{i,j}\) 的生成函数,那么:

\[F_i(x)=(1+x+\cdots+x^{i-1})F_{i-1}(x) \]

于是:

\[F_n(x)=\prod_{i=1}^{n}(1+x+\cdots+x^{i-1})=\prod_{i=1}^{n}\dfrac{1-x^i}{1-x}=\dfrac{\prod\limits_{i=1}^n(1-x^i)}{(1-x)^n} \]

我们要求的就是上式的 \(k\) 次项系数。这个式子为多个单项式求积,考虑先取 \(\ln\)\(\exp\)

\[\ln F_n(x)=\sum_{i=1}^n\ln(1-x^i)-n\ln(1-x) \]

其中对 \(\ln(1-x^a)\) 求导貌似是个很套路的东西:先求导再积分:

\[\begin{aligned} \ [\ln(1-x^a)]'&=\dfrac{-ax^{a-1}}{1-x^a}=\sum_{i=0}^{\infty}-ax^{ai+a-1}\\ \ln(1-x^a)&=\sum_{i=0}^{\infty}-\dfrac{a}{ai+a}x^{ai+a}=\sum_{i=1}^{\infty}-\dfrac{x^{ai}}{i} \end{aligned} \]

那么直接对于每个 \(a\) 枚举 \(a\) 的倍数即可,时间复杂度是调和级数级别的。

最后再对 \(\ln F_n(x)\)\(\exp\)\(k\) 次项即为答案。总时间复杂度 \(O(n\log n)\)

法二:

即为求下式的解的方案数:

\[\sum_{i=1}^n x_i=k \]

其中需要满足 \(x_i\in[0,i-1]\)

这个限制不好做,考虑容斥,钦定一些位置不满足:

\[ans=\sum_{S\subseteq\{1,\cdots,n\}}(-1)^{|S|}\times c\left(k-\sum_{i\in S}i\right) \]

解释一下这条式子,枚举对于 \(i\in S\) 不满足限制,也就是 \(x_i\geq i\),不妨设 \(y_i=x_i-i\),只需满足 \(y_i\geq 0\) 即可,那么对于 \(i\in S\)\(y_i\)\(i\not\in S\)\(x_i\) 的限制都只有 \(\geq 0\)

于是 \(c(k)\) 的定义也显而易见了:\(\sum\limits_{i=1}^nz_i=k\) 的方案数,其中限制只有 \(z_i\geq 0\)\(c(k)\) 通过插板法容易得到。

考虑 DP,暴力的想法是考虑完 \(1\sim n\) 中的数,选了 \(i\) 个不同的数(\(|S|=i\)),和为 \(j\)\(\sum\limits_{p\in S}p=j\))的方案数,尽管 \(i\) 的取值范围只有 \(\sqrt k\),但时间复杂度还是 \(O(nk\sqrt k)\),甚至比暴力还慢。

神奇的方法是使用旋转体积背包:设 \(g_{i,j}\) 表示从 \(1\sim n\) 中选 \(i\) 个不同的数,他们的和为 \(j\) 的方案数。

每次转移可以考虑集体垫高一层,然后再看是否还要在末尾加上一列:

\[\begin{aligned} g_{i,j}&\to g_{i,j+i}\\ g_{i,j}&\to g_{i+1,j+i+1} \end{aligned} \]

注意对于算出来的 \(g_{i,j}\) 要减掉 \(g_{i-1,j-(n+1)}\),这是为了防止选的数大于 \(n\) 而减掉垫高一层后最高一列为 \(n+1\) 后面的列高度 \(\leq n\) 的方案(\(g_{i,j}\) 一定是由某个状态垫高一层转移过来,而转移过来的状态已经保证了所有数小于等于 \(n\))。

时间复杂度 \(O(k\sqrt k)\)

#include<bits/stdc++.h>

#define SN 450
#define N 100010

using namespace std;

namespace modular
{
    const int mod=1000000007;
    inline int add(int x,int y){return x+y>=mod?x+y-mod:x+y;}
    inline int dec(int x,int y){return x-y<0?x-y+mod:x-y;}
    inline int mul(int x,int y){return 1ll*x*y%mod;}
    inline void Add(int &x,int y){x=x+y>=mod?x+y-mod:x+y;}
    inline void Dec(int &x,int y){x=x-y<0?x-y+mod:x-y;}
}using namespace modular;

inline int poww(int a,int b)
{
    int ans=1;
    while(b)
    {
        if(b&1) ans=mul(ans,a);
        a=mul(a,a);
        b>>=1;
    }
    return ans;
}

inline int read()
{
    int x=0,f=1;
    char ch=getchar();
    while(ch<'0'||ch>'9')
    {
        if(ch=='-') f=-1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9')
    {
        x=(x<<1)+(x<<3)+(ch^'0');
        ch=getchar();
    }
    return x*f;
}

int n,k,g[SN][N];
int fac[N<<1],ifac[N<<1];

int C(int n,int m)
{
    return mul(mul(fac[n],ifac[m]),ifac[n-m]);
}

int calc(int s)
{
    return C(n+s-1,n-1);
}

int main()
{
    n=read(),k=read();
    fac[0]=1;
    for(int i=1;i<=n+k;i++) fac[i]=mul(fac[i-1],i);
    ifac[n+k]=poww(fac[n+k],mod-2);
    for(int i=n+k;i>=1;i--) ifac[i-1]=mul(ifac[i],i);
    g[0][0]=g[1][1]=1;
    for(int i=1;i<=n&&i*(i+1)/2<=k;i++)
    {
        for(int j=i*(i+1)/2;j<=k;j++)
        {
            if(j>=n+1) Dec(g[i][j],g[i-1][j-n-1]);
            if(j+i<=k) Add(g[i][j+i],g[i][j]);
            if(j+i+1<=k) Add(g[i+1][j+i+1],g[i][j]);
        }
    }
    int ans=0;
    for(int i=0;i<=n&&i*(i+1)/2<=k;i++)
    {
        for(int j=i*(i+1)/2;j<=k;j++)
        {
            if(i&1) Dec(ans,mul(g[i][j],calc(k-j)));
            else Add(ans,mul(g[i][j],calc(k-j)));
        }
    }
    printf("%d\n",ans);
    return 0;
}
posted @ 2022-10-29 11:05  ez_lcw  阅读(51)  评论(0)    收藏  举报