loj 6077/bzoj 2431

首先我们考虑一个暴力的dp:

我们从小到大加入每个数,当我们加入第$i$个数时,可能产生的逆序对数量是$[0,i-1]$(这个证明考虑把第$i$个数放在哪即可),这样可以列出一个递推式:

设状态$dp[i][j]$表示已经加到了第$i$个数,此时的逆序对个数为$j$,那么有转移:$dp[i][j]=sum_{k=j-i+1}^{j}dp[i-1][k]$

这个转移的时间复杂度是$O(n^{3})$

然后考虑优化,显然那个求和式可以用前缀和优化,时间复杂度降成$O(n^{2})$

这样已经可以通过bzoj上的题目了,但loj上的是加强版,过不去

考虑其他做法:

(如果您不喜欢多项式可以直接跳到解法2)

首先,考虑生成函数:对每个位置构造一个生成函数,那么最后的结论就是:

$F(x)=prod_{i=0}^{n-1}(sum_{j=0}^{i}x^{j})$

整理一下后面,就是:

$F(x)=prod_{i=0}^{n-1}frac{1-x^{i+1}}{1-x}$

然后整体合并一下,就得到:

$F(x)=frac{prod_{i=1}^{n}(1-x^{i})}{(1-x)^{n}}$

有点奇怪,两边取下对数:

$lnF(x)=sum_{i=1}^{n}ln(1-x^{i})-nln(1-x)$

$ln(1-x^{i})$这个东西已经展开过很多次了...

对$ln(1-x^{i})$求导得到:

$frac{-ix^{i-1}}{1-x^{i}}$

把下半部分恢复成等比数列求和的形式:

$-ix^{i-1}sum_{j=0}^{∞}x^{ij}$

把外面的系数移进去:

$-sum_{j=0}^{∞}ix^{ij+(i-1)}$

然后积分:

$int -sum_{j=0}^{∞}ix^{ij+(i-1)}=-sum_{j=1}^{∞}frac{i}{ij+i}x^{ij+i}$

约分一下,就得到了:

$-sum_{j=0}^{∞}frac{1}{j+1}x^{ij+i}$

令$j=j+1$,有:

$-sum_{j=1}^{∞}frac{1}{j}x^{ij}$

对一个函数先求导再积分得到的就是原函数,因此有:

$ln(1-x^{i})=-sum_{j=1}^{∞}frac{1}{j}x^{ij}$

这样的话可以通过枚举倍数做到$O(nlnn)$(即调和级数)求出系数,然后多项式exp即可,注意这里由于模数不好,需要用到MTT,总时间复杂度仍为$O(nlog_{2}n)$

其实后面的部分基本与这道题一致,基本步骤也相同

但是...由于过于毒瘤,我并不想写一遍...

(更何况还多了一大堆东西)

因此我们考虑做法二

原来的dp已经被压榨到足够优秀,剩下的部分很困难了,因此我们考虑转化问题:

回到最开始的思想:

我们从小到大加入每个数,当我们加入第$i$个数时,可能产生的逆序对数量是$[0,i-1]$

那么,如果我们设$x_{i}$表示加入第$i$个数时产生的逆序对数量,我们实际只是在解这个不定方程:

$sum_{i=1}^{n}x_{i}=k$

其中对任意$iin [1,n]$,有$x_{i}in [0,i-1]$

我们实际是在求这个不定方程解的组数!

每个变量都有上界,这很不好求...

因此我们考虑容斥,容斥系数-1

还是要容斥的...

我们不妨假设有某个位置不合法,设这个位置为$p$,那么显然有表达式:

$x_{p}geq p-1$

那么我们考虑一个增量$delta_{p}=x_{p}-p+1$,那么我们在等式两侧同时去掉这个增量,新的变量记作$x_{p}^{'}$,转化后的方程即为:

$x_{1}+x_{2}+...+x_{p}^{'}+...+x_{n}=k-delta_{p}$

然而,在这种情况下,我们事实上仍然无法保证剩下的位置均合法,因此这样统计出的是有重复的!

据此,我们进一步分析:如果我们先统计出总的增量$delta$,然后将这个增量分配给几个单独的$delta_{p}$即可,然后用容斥原理计算就可以了

设状态$f[i][j]$表示用$i$个$[1,n]$之间的数求和,和为$j$的方案数,那么每一个总增量$delta$对答案的贡献即为$C_{n+k-delta-1}^{n-1}(sum_{i=0}^{sqrt{k}}(-1)^{i}f[i][delta])$

为什么上界是$sqrt{k}$?

考虑$m$个互不相同的数求和的最小值为$frac{m(m+1)}{2}$,由于$delta leq k$,因此有效的数字个数应当是$sqrt{k}$级别的

前面乘的组合数也就是剩下的那个不定方程的解的组数,后面是容斥方案数

这样的话我们只需计算出$f[i][j]$即可

考虑转移:由于我们要求数组中每个数都不同,所以可以把操作看成对数组中每个数加一,因此有转移:

$f[i][j]=f[i][j-i]+f[i-1][j-1]$

原理:如果元素个数不变,那么每个数加一之前的值即为$f[i][j-i]$

如果元素个数改变,那么一定是原数组中每个数加一之后再放下一个$1$,从$f[i-1][j-i]$转移过来

但是我们注意到,每个元素有一个上界就是$n$,但是这样直接算很有可能某个元素超过了$n$!

我们注意到每个元素+1的次数不能超过$n$,因此我们最后还需要去掉一个超过n的情况,也就是加上1之后某个数的大小超过$n$,变成了$n+1$!

最终的表达式即为$f[i][j]=f[i][j-i]+f[i-1][j-i]-f[i-1][j-n-1]$

这样这题就算做完了

当然其实还有方法三

生成函数结合容斥原理

考虑上面那个生成函数,发现我们要求的只是一个系数,因此我们考虑能不能直接搞出来

先考虑分母:

$frac{1}{(1-x)^{n}}=(sum_{i=0}^{infty}x^{i})^{n}=sum_{i=0}^{infty} C_{n+i-1}^{n-1}x^{i}$

再考虑分子:

$prod_{i=1}^{n}(1-x^{i})$

如果我们展开这个东西,那么$x^{i}$项前的系数即为用$j$个互不相同的$[1,n]$的数表示出$i$的方案数再乘一个$(-1)^{j}$

因此我们考虑直接计算这个东西

设状态$f[i][j]$表示用$i$个互不相同的数表示出和为$j$的方案数

然而由于选出的数不能重复,因此这个dp根本搞不了

考虑进一步转化问题:

如果我们给一个序呢?

我们设状态$f[i][j]$表示用$i$个互不相同的数表示出和为$j$的方案数,要求构造出序列{$a_{i}$}的是个上升序列

这样的话我们考虑对序列翻转后差分,设{$a_{i}$}是反转后的序列,令$b_{i}=a_{i}-a_{i+1}$,那么每个$b_{i}$对答案的贡献即为$ib_{i}$

这样的话我们只需构造出序列$b$即可

那么我们设状态$f[i][j]$表示已经放了$i$个$b$,总贡献为$j$的方案数,那么这个就有以下几个转移方向:

首先:个数不变,第$i$个$b$加一,那么他的贡献是$i$,因此$f[i][j]+=f[i][j-i]$

其次:个数改变,在位置$i$上放了个1,那么其共享仍为$i$,因此$f[i][j]+=f[i-1][j-i]$

但是,每个位置上的数都不应当超过$n$,但我们是逐次加1来增大的$b$序列,因此如果出现大于$n$的情况,那么一定是出现了$n+1$!

因此我们去掉这个$n+1$即可,转移即为$f[i][j]-=f[i-1][j-n-1]$

这样$f[i][j]$就搞出来了,可以发现这与最初的问题等价

这样我们最后卷积算出$k$位置的系数就好了

(可以看到方法二、三思想稍有区别,但殊途同归,代码其实是一样的qwq)

#include <cstdio>
#include <cmath>
#include <cstring>
#include <cstdlib>
#include <iostream>
#include <algorithm>
#include <queue>
#include <stack>
#define ll long long
using namespace std;
const ll mode=1000000007;
const int lim=450;
ll inv[200005];
ll minv[200005];
ll mul[200005];
ll dp[455][100005];
ll n,k;
void init()
{
    inv[0]=inv[1]=mul[0]=mul[1]=minv[0]=minv[1]=1;
    for(int i=2;i<=200000;i++)
    {
        inv[i]=(mode-mode/i)*inv[mode%i]%mode;
        minv[i]=minv[i-1]*inv[i]%mode;
        mul[i]=mul[i-1]*i%mode;
    }
}
ll C(ll x,ll y)
{
    if(x<y)return 0;
    return mul[x]*minv[y]%mode*minv[x-y]%mode;
}
int main()
{
    init();
    scanf("%lld%lld",&n,&k);
    dp[0][0]=1;
    for(int i=1;i<=lim;i++)
    {
        for(int j=0;j<=k;j++)
        {
            if(j>=i)dp[i][j]=(dp[i][j-i]+dp[i-1][j-i])%mode;
            if(j>=n+1)dp[i][j]=(dp[i][j]+mode-dp[i-1][j-n-1])%mode;
        }
    }
    ll ans=0;
    for(int i=0;i<=k;i++)
    {
        ll temps=0;
        ll f=1;
        for(int j=0;j<=lim;j++)temps=(temps+f*dp[j][i]+mode)%mode,f=-f;
        temps=temps*C(n+k-i-1,n-1)%mode;
        ans=(ans+temps)%mode;
    }
    printf("%lld
",ans);
    return 0;
}
原文地址:https://www.cnblogs.com/zhangleo/p/11135016.html