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;
    }
  • 相关阅读:
    hihocoder1062 最近公共祖先·一
    POJ2342 Anniversary party(动态规划)(树形DP)
    【动态规划】抄近路(水题)
    【动态规划】数的划分 (动态规划)
    【动态规划】矩形嵌套 (DGA上的动态规划)
    hihocoder Popular Products(STL)
    hihocoder Counting Islands II(并查集)
    51nod 编辑距离问题(动态规划)
    51nod 最长公共子序列问题(动态规划)(LCS)(递归)
    目标提取——背景均匀、目标与背景相似
  • 原文地址:https://www.cnblogs.com/Maxwei-wzj/p/9793280.html
Copyright © 2011-2022 走看看