zoukankan      html  css  js  c++  java
  • 【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
    ",ans);
        }
    
        return 0;
    }
  • 相关阅读:
    使用 libevent 和 libev 提高网络应用性能
    An existing connection was forcibly closed by the remote host
    各种浏览器的兼容css
    vs输出窗口,显示build的时间
    sass
    网站设置404错误页
    List of content management systems
    css footer not displaying at the bottom of the page
    强制刷新css
    sp_executesql invalid object name
  • 原文地址:https://www.cnblogs.com/Maxwei-wzj/p/9793280.html
Copyright © 2011-2022 走看看