• P4769[NOI2018]冒泡排序【组合数学,树状数组】


    正题

    题目链接:https://www.luogu.com.cn/problem/P4769


    题目大意

    有一个冒泡排序的算法

    输入:一个长度为 n 的排列 p[1...n]
    输出:p 排序后的结果。
    for i = 1 to n do
    	for j = 1 to n - 1 do
    		if(p[j] > p[j + 1])
    			交换 p[j] 与 p[j + 1] 的值
    

    然后给出一个排列\(a\),求在所有字典序大于\(a\)的排列\(p\)中冒泡排序交换次数恰好为\(\sum_{i=1}^n|i-p_i|\)的排列数。

    \(1\leq n\leq 6\times 10^5,\sum n\leq 2\times 10^6\)


    解题思路

    打一下表发现合法的排列条件是最长下降子序列不超过\(2\)

    然后我们先不考虑字典序限制条件怎么做,我们设\(f_{i,1/2}\)表示目前下降子序列长度为\(1/2\)中末尾最大的那个。

    那么\(f_{i,1}\)就是目前出现的数中最大的,然后如果我们从前往后填数,那么如果\(\leq f_{i,2}\)的数中有没有填进去的,肯定不合法,所以\(f_{i,2}\)肯定比目前没有填进去的数中所有数字都小,不需要考虑。

    \(g_{i,j}\)表示目前还剩下\(i\)个数没填,其中\(f_{i,1}\)大于其中的\(j\)个数,那么有\(g_{i,j}\)可以转移到\(g_{i-1,j-1}\)(填在最底)和\(g_{i-1,k}(k\geq j)\)(填在\(j\)上面)。

    我们考虑快速的求出每个\(g\),反过来就是\(g_{i,j}\)转移到\(g_{i+1,j+1}\)\(g_{i+1,j}\)

    我们维护一个\(h_{i,j}=g_{i,i-j}\),那么每次的转移就是\(h_{i,j}\)转移到\(h_{i,k}(j\leq k\leq i)\)

    这个转移很像卡特兰数的要求,每次可以往下或者往右,但是不能超过对角线。

    这样来说移动到位置\((n,m)(m\leq n)\)的话方案数就是\(\binom{n+m}{m}-\binom{n+m}{m-1}\)

    然后就是枚举第一个超过该字典序的位置,这样前面的方案固定,剩下的数可以用树状数组计算得出,再用组合数求答案即可。

    时间复杂度:\(O(n\log n)\)


    code

    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #define ll long long
    #define lowbit(x) (x&-x) 
    using namespace std;
    const ll N=6e5*2,P=998244353;
    ll T,n,t[N],a[N],fac[N],inv[N],ans;
    ll C(ll n,ll m)
    {if(m<0)return 0;return fac[n]*inv[m]%P*inv[n-m]%P;}
    void Change(ll x,ll val){
    	while(x<=n){
    		t[x]+=val;
    		x+=lowbit(x);
    	}
    	return;
    }
    ll Ask(ll x){
    	ll ans=0;
    	while(x){
    		ans+=t[x];
    		x-=lowbit(x);
    	}
    	return ans;
    }
    ll F(ll n,ll m){
    	m=n-1-m;if(!m)return 0;m--;
    	return (C(n+m,m)-C(n+m,m-1)+P)%P;
    }
    signed main()
    {
    //	freopen("inverse3.in","r",stdin);
    	fac[0]=inv[0]=inv[1]=1;
    	for(ll i=2;i<N;i++)inv[i]=P-inv[P%i]*(P/i)%P;
    	for(ll i=1;i<N;i++)fac[i]=fac[i-1]*i%P,inv[i]=inv[i-1]*inv[i]%P;
    	scanf("%lld",&T);
    	while(T--){
    		scanf("%lld",&n);ans=0;
    		for(ll i=1;i<=n;i++)
    			scanf("%lld",&a[i]),Change(a[i],1);
    		for(ll i=1,mx=0;i<=n;i++){
    			Change(a[i],-1);mx=max(mx,a[i]);
    			(ans+=F(n-i+1,Ask(mx)))%=P;
    			if(a[i]<mx&&Ask(a[i]))
    				break;
    		}
    		for(int i=1;i<=n;i++)t[i]=0;
    		printf("%lld\n",ans);
    	}
    	return 0; 
    }
    
  • 相关阅读:
    OpenWrt的luci web管理器添加新菜单
    Lua基础
    2016年1月25日 《1024伐木累》-小白篇之开发网站,三天!(中篇-2奇怪的IE)-总章节十一
    《1024伐木累》-程序员妹子与花木兰
    《1024伐木累》-关注女神小号,藏大钱
    《1024伐木累》-小白篇之开发网站,三天!(中篇-1)-总章节十
    《1024伐木累》-炒股赚钱,大保健
    《1024伐木累》-找规律,女生不讲卫生~
    微信支付开发-Senparc.Weixin.MP详解
    《1024伐木累》-小白篇之开发网站,三天!(前篇)-总章节八
  • 原文地址:https://www.cnblogs.com/QuantAsk/p/16420269.html
Copyright © 2020-2023  润新知