【BZOJ5416】冒泡排序(NOI2018)-组合数学+树状数组

测试地址:冒泡排序
做法:本题需要用到组合数学+树状数组。
一道神题,用到的数学知识并没有难到哪里去,但成功把我这种弱菜区分掉了。
首先,交换次数能达到题目中给的下界的充要条件是,排列中不存在长度3的下降子序列。因为要达到下界,每次交换都应该要“达到效果”,即两边的元素都往该去的方向移动。而一旦出现长度为3的下降子序列,就一定存在一次交换,使得对一个元素没达到效果,因此也就达不到下界了。
转换了问题之后,我们需要找到构造出合法序列的方法。假设目前构造到第i位,之前所填的数的最大值为j,那么对于当前填的数,我们当然可以填任意一个大于j的数,而对于小于j的数,它必须是还没填的数中最小的数,否则令这个最小的数为x,当前填的数为pi,排列中一定会存在子序列j>pi>x,也就不满足条件了。
先考虑没有任何限制的情况,令f(i,j)为还有i个数要填,有j个数比当前已填数中的最大值大(后面简称这种数为非限制元素),这样的总的方案数。由上面的分析可得:
f(i,j)=k=0jf(i1,jk) (ij)
f(i,j)=0 (i<j)
边界条件为f(0,0)=1
注意到f(i,j)是一个前缀和的形式,因此有:
f(i,j)=f(i,j1)+f(i1,j)
边界条件为f(i,0)=1
有了这个递推式,f(i,j)是不是就等于,从(0,0)每次向右或向上走一个单位长度到(i,j)的路径条数:Ci+jj呢?不是的,因为我们还限制了f(i,j)(i<j)=0,所以路径不能跨越x=y这条直线。这个就有点像卡特兰数了,事实上,没有任何限制的方案数f(n,n)的确就是卡特兰数C(n),但是对于更一般的f,我们也可以用类似的证明方法得到f(i,j)=Ci+jjCi+jj1
于是我们在O(n)预处理阶乘及逆元的情况下,可以O(1)计算一个f(i,j)了。
接下来我们考虑字典序的限制。不难看出,我们可以枚举前缀,然后求最长公共前缀为该前缀的合法排列数量,显然此时要填的数应该比给出的排列中的数大。我们又知道,填入的数应该同时比前面已填的最大数要大,因此这一位可填的数就一定要比这一位的前缀最大值大。前缀最大值很明显是可以预处理的,而要求某一个后缀中比某数大的数的数量显然用树状数组就可以了。
那么此时,如果前缀中已经出现了n,即最大的数值,则后面剩下的数显然只能从小到大填了,而这种情况填出的排列显然字典序不会比给出的排列大,因此直接退出即可。
否则,假设当前在填第i位,如果后面有k个非限制元素可以填,那么就对答案有x=0k1f(ni,x)的贡献。根据f的定义,这显然等于f(ni+1,k1)。前面已经介绍了O(1)计算这个的方法,因此直接累加即可。
上面就是对答案贡献的讨论,然而我们还需要判断一个前缀合不合法,这个就很容易了,有很多种方法判断,判断的根据在上面讲构造算法的时候已经说明了,利用树状数组可以做到O(logn)判断。
于是我们就解决了这一题,时间复杂度为O(nlogn)
以下是本人代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=998244353;
int T,tot,n,a[600010],b[600010],c[600010],sum[600010];
ll fac[1200010],inv[1200010],invfac[1200010];

void extend(int limit)
{
    for(ll i=tot+1;i<=limit;i++)
    {
        fac[i]=fac[i-1]*i%mod;
        inv[i]=(mod-mod/i)*inv[mod%i]%mod;
        invfac[i]=invfac[i-1]*inv[i]%mod;
    }
}

int lowbit(int i)
{
    return i&(-i);
}

void add(int x)
{
    for(int i=x;i<=n;i+=lowbit(i))
        sum[i]++;
}

int calc(int x)
{
    int ans=0;
    for(int i=x;i;i-=lowbit(i))
        ans+=sum[i];
    return ans;
}

ll C(int a,int b)
{
    return fac[a]*invfac[b]%mod*invfac[a-b]%mod;
}

ll f(int a,int b)
{
    return (C(a+b,b)-C(a+b,b-1)+mod)%mod;
}

int main()
{
    scanf("%d",&T);

    tot=1;
    fac[0]=invfac[0]=fac[1]=invfac[1]=inv[1]=1;
    for(int i=1;i<=T;i++)
    {
        scanf("%d",&n);
        extend(n<<1);

        for(int i=1;i<=n;i++)
            scanf("%d",&a[i]);
        for(int i=1;i<=n;i++)
            sum[i]=0;
        for(int i=1;i<=n;i++)
        {
            b[i]=calc(a[i]);
            add(a[i]);
        }
        for(int i=1;i<=n;i++)
            sum[i]=0;
        for(int i=n;i>=1;i--)
        {
            c[i]=n-i-calc(a[i]);
            add(a[i]);
        }

        int now=n;
        ll ans=0;
        for(int i=1;i<=n;i++)
        {
            bool flag=0;
            if (c[i]<now) now=c[i],flag=1;
            if (now==0) break;
            ans=(ans+f(n-i+1,now-1))%mod;
            if (flag) continue;
            if (b[i]==a[i]-1) continue;
            break;
        }
        printf("%lld\n",ans);
    }

    return 0;
}
posted @ 2018-08-18 13:37  Maxwei_wzj  阅读(97)  评论(0编辑  收藏  举报