[2017 山东一轮集训 Day7]逆序对

壹、题目描述 ¶

传送门 to LOJ.

贰、题解

本来有一种十分完美的生成函数解法。

我们不难发现,最后的多项式实际上就是:
∏ i = 1 n 1 − x i 1 − x \prod_{i=1}^n{1-x^i\over 1-x} i=1n1x1xi
我们可以将分子全部拿出来看看:
∏ i = 1 n ( 1 − x i ) \prod_{i=1}^n(1-x^i) i=1n(1xi)
这不禁让人想起了付公主的背包,使用类似的方法进行化简:
( ∗ ) = exp ⁡ ( ∑ i = 1 n ln ⁡ ( 1 − x i ) ) = exp ⁡ ( ∑ i = 1 n ∫ − i x i − 1 1 − x i d x ) = exp ⁡ ( ∑ i = 1 n ∫ ∑ j = 0 ∞ − i x i − 1 × x i j d x ) = exp ⁡ ( ∑ i = 1 n ∑ j = 0 ∞ − i x i ( j + 1 ) i ( j + 1 ) ) = exp ⁡ ( ∑ i = 1 n ∑ j = 1 ∞ − x i j j ) \begin{aligned} (*)&=\exp\left(\sum_{i=1}^n\ln(1-x^i)\right) \\ &=\exp\left(\sum_{i=1}^n\int{-ix^{i-1}\over 1-x^i}dx\right) \\ &=\exp\left(\sum_{i=1}^n\int\sum_{j=0}^\infty-ix^{i-1}\times x^{ij}dx\right) \\ &=\exp\left(\sum_{i=1}^n\sum_{j=0}^\infty {-ix^{i(j+1)}\over i(j+1)}\right) \\ &=\exp\left(\sum_{i=1}^n\sum_{j=1}^\infty{-x^{ij}\over j}\right) \end{aligned} ()=exp(i=1nln(1xi))=exp(i=1n1xiixi1dx)=exp(i=1nj=0ixi1×xijdx)=exp(i=1nj=0i(j+1)ixi(j+1))=exp(i=1nj=1jxij)
exp ⁡ \exp exp 里面的东西是 O ( k ln ⁡ k ) \mathcal O(k\ln k) O(klnk) 的,做 exp ⁡ \exp exp O ( k log ⁡ k ) \mathcal O(k\log k) O(klogk) 的,最后的复杂度就是 O ( k log ⁡ k + k ln ⁡ k ) \mathcal O(k\log k+k\ln k) O(klogk+klnk).

注意别忘记了还有一个 1 ( 1 − x ) n 1\over (1-x)^n (1x)n1.

但是由于   m o d   = 1 0 9 + 7 \bmod=10^9+7 mod=109+7,除非你打三模 N T T \rm NTT NTT 或者 F F T \rm FFT FFT 或者 M T T \rm MTT MTT.


还有另一种做法,对最本质的问题使用容斥 —— 钦定一些 i i i 使得 D ( x ) ≥ x D(x)\ge x D(x)x. 假定他们的和为 s s s,通过隔板法,不难发现分配剩下的数的方案数就是 ( k − s + n − 1 n − 1 ) {k-s+n-1\choose n-1} (n1ks+n1).

那么,现在问题是,如何从 1 , 2 , 3 , ⋯ n 1,2,3,\cdots n 1,2,3,n 中选择 i i i 个数使得他们的和刚好为 s s s ?这里有一个很妙的 D P \rm DP DP

f ( i , j ) f(i,j) f(i,j) 表示当前有 i i i 个数,和为 j j j.

考虑转移:

  • j ≥ i j\ge i ji,那么我们可以考虑:
    • 将当前所有数都加上 1 1 1,方案数加上 f ( i , j − i ) f(i,j-i) f(i,ji)
    • i i i 个数是刚刚插入的,将前 i − 1 i-1 i1 个数加 1 1 1 之后在末尾放上一个 1 1 1,方案数加上 f ( i − 1 , j − i ) f(i-1,j-i) f(i1,ji)
  • j > n j>n j>n,那么可能出现最大的数变成 n + 1 n+1 n+1 的情况,所以考虑将这种情况减掉,减去 f ( i − 1 , j − n − 1 ) f(i-1,j-n-1) f(i1,jn1)

该转移妙在,我们维护的是一个动态的过程,可以在序列中加数,但是在加数之前,要先将前面的所有数平移一位,以保证所有的数互不相同。

其实也可以从差分数组角度理解吧?

以上 D P \rm DP DP O ( k k ) \mathcal O(k\sqrt k) O(kk ) 的,总复杂度就是这个咯。

艹,傻 [哔] 出题人,不给 N T T \rm NTT NTT 模数,出题人我 [哔][哔]

另,计算答案时不要忘记系数。

叁、参考代码 ¶

#include<cstdio>
#include<algorithm>
#include<cstdlib>
#include<vector>
using namespace std;

// #define NDEBUG
#include<cassert>

namespace Elaina{
    #define rep(i, l, r) for(int i=(l), i##_end_=(r); i<=i##_end_; ++i)
    #define drep(i, l, r) for(int i=(l), i##_end_=(r); i>=i##_end_; --i)
    #define fi first
    #define se second
    #define mp(a, b) make_pair(a, b)
    #define Endl putchar('\n')
    #define mmset(a, b) memset(a, b, sizeof a)
    // #define int long long
    typedef long long ll;
    typedef unsigned long long ull;
    typedef pair<int, int> pii;
    typedef pair<ll, ll> pll;
    template<class T>inline T fab(T x){ return x<0? -x: x; }
    template<class T>inline void getmin(T& x, const T rhs){ x=min(x, rhs); }
    template<class T>inline void getmax(T& x, const T rhs){ x=max(x, rhs); }
    template<class T>inline T readin(T x){
        x=0; int f=0; char c;
        while((c=getchar())<'0' || '9'<c) if(c=='-') f=1;
        for(x=(c^48); '0'<=(c=getchar()) && c<='9'; x=(x<<1)+(x<<3)+(c^48));
        return f? -x: x;
    }
    template<class T>inline void writc(T x, char s='\n'){
        static int fwri_sta[1005], fwri_ed=0;
        if(x<0) putchar('-'), x=-x;
        do fwri_sta[++fwri_ed]=x%10, x/=10; while(x);
        while(putchar(fwri_sta[fwri_ed--]^48), fwri_ed);
        putchar(s);
    }
}
using namespace Elaina;

const int mod=1e9+7;
const int maxk=2e5;
const int sqrtk=450;

inline int qkpow(int a, int n){
    int ret=1;
    for(; n>0; n>>=1, a=1ll*a*a%mod)
        if(n&1) ret=1ll*ret*a%mod;
    return ret;
}

int fac[maxk+5], finv[maxk+5];
inline void prelude(){
    fac[0]=finv[0]=1;
    for(int i=1; i<=maxk; ++i)
        fac[i]=1ll*fac[i-1]*i%mod;
    finv[maxk]=qkpow(fac[maxk], mod-2);
    for(int i=maxk-1; i>=1; --i)
        finv[i]=1ll*finv[i+1]*(i+1)%mod;
}
inline int C(int n, int m){
    if(n<m) return 0;
    return 1ll*fac[n]*finv[m]%mod*finv[n-m]%mod;
}

int n, k, m=450;

int f[sqrtk+5][maxk+5];

inline void getf(){
    f[0][0]=1;
    for(int i=1; i<=m; ++i){
        for(int j=i; j<=k; ++j){
            if(j>=i) f[i][j]=(0ll+f[i][j-i]+f[i-1][j-i])%mod;
            if(j>n) f[i][j]=(0ll+f[i][j]+mod-f[i-1][j-n-1])%mod;
        }
    }
}

#define sign(i) (((i)&1)? -1: 1)
inline void getans(){
    int ans=0;
    for(int s=0; s<=k; ++s){
        int cnt=0;
        for(int j=0; j<=m; ++j)
            cnt=(0ll+cnt+mod+sign(j)*f[j][s])%mod;
        ans=(0ll+ans+mod+1ll*C(k-s+n-1, n-1)*cnt%mod)%mod;
    }
    writc(ans);
}

signed main(){
    prelude();
    n=readin(1), k=readin(1);
    getf();
    getans();
    return 0;
}

肆、关键之处 ¶

不得不说,这种转移真的很巧妙,转移一个 “动态” 的数组,操作有二:

  • 将数组中每个元素加一;
  • 将数组中每个元素加一之后,在末尾放入一个 1 1 1

第一个操作是为了处理数中间有“断层”的情况,第二个操作是为了加入元素,并且保证元素两两不同。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值