zoukankan      html  css  js  c++  java
  • 【洛谷4769】[NOI2018] 冒泡排序(DP的组合意义)

    点此看题面

    • 已知一个排列进行冒泡排序需要交换次数的下界为(frac12sum_{i=1}^n|i-p_i|)
    • 定义一个冒泡排序次数能达到下界的排列为好的排列。
    • 给定一个长度为(n)的排列,求字典序严格大于该排列的好的排列个数。
    • (nle6 imes10^5,sum nle2 imes10^6)

    好排列的充要条件

    要达到下界说明不会出现无用的交换。

    而要出现无用的交换,当且仅当有一个数左边存在比它大的数且右边存在比它小的数。

    因为如果这样,左边的数移到右边和右边的数移到左边的过程中,这个数的移动就会相互抵消。

    这个条件的另一种表述就是不存在长度大于等于(3)的下降子序列,进一步也就等价于原序列可以拆分为两个上升子序列。

    不考虑限制条件

    考虑一次拆分的具体过程,方便起见把当前所有数中的最大值所在序列称为(A)序列,另一个则称为(B)序列。

    由于所有数都需要一个归宿,因此在接下来的填写中,对于大于最大值的数我们仍然可以自由填写加入(A)序列,而对于小于最大值的那些未填的数,我们必须把它们按照从小到大的唯一顺序加入(B)序列。

    所以说,如果设(f_{i,j})表示剩余(i)个数,其中有(j)个数大于最大值时的方案数,显然(jle i)

    考虑转移,一种情况是选择一个更大的数加入(A)序列,得出转移:(sum_{k=0}^{j-1}f_{i-1,k})

    另一种情况是选择一个较小的数加入(B)序列,选法唯一,得出转移:(f_{i-1,j})

    把两种转移结合起来得到:

    [f_{i,j}=sum_{k=0}^jf_{i-1,k} ]

    发现这相当于是一个前缀和的形式,所以可以改写成:

    [f_{i,j}=f_{i,j-1}+f_{i-1,j} ]

    这是一个典型的坐标系上走路式转移,从((0,0))一路走到((i,j))的方案数应该是(C_{i+j}^i)

    但由于(jle i)的限制还需要减去非法方案数,即考虑((i,j))关于(y=x-1)的对称点((j-1,i+1)),走到那里的方案数为(C_{i+j}^{j-1})

    因此联合起来就可以通过组合数来表示得到:

    [f_{i,j}=C_{i+j}^j-C_{i+j}^{j-1} ]

    字典序的限制

    考虑枚举与给定序列的第一个不同位,那么只要枚举每一位先计算当前位比给定排列大的好排列个数,然后强制这一位和给定排列相同即可。

    假设给定排列的当前位上的值是(a_i),由于之前的每一位都已经确定和给定的排列相同,所以我们可以利用树状数组轻松求出之前比(a_i)小的数的个数(p),然后计算出尚未填写的比(a_i)大的数的个数(q)

    首先我们发现,如果当前数成为了新的最大值,那么此时的(q)必然比之前的全部(q)都要更小,所以可以用一个变量(t)来维护(q)的最小值。

    如果某一时刻(t)变成了(0),显然不可能构造出比给定序列字典序更大的排列了。

    否则,考虑计算此时的方案数,由于要大于当前位置上的数,因此填入的不可能是剩余数中最小的数,无法加入(B)序列,只能从这(t)个数中选择一个加入(A)序列。

    因此,类似于先前的(DP)转移,相当于是要填写剩下的这个长度为(n-i)的序列:(sum_{j=0}^{t-1}f_{n-i,j})

    根据先前得出的(f)数组为前缀和形式的结论,这玩意就等于(f_{n-i+1,t-1}),可以直接利用先前推出的组合数公式计算。

    最后我们考虑计算完答案后强制这一位选(a_i)的过程,如果(a_i)是新的最大值那么直接加入(A)序列即可;否则因为(B)序列需要满足尚未填写的数从小到大被加入,因此比(a_i)小的数必须全部存在,即(p=a_i-1)。如果不合法,直接终止枚举即可。

    代码:(O(nlogn))

    #include<bits/stdc++.h>
    #define Tp template<typename Ty>
    #define Ts template<typename Ty,typename... Ar>
    #define Reg register
    #define RI Reg int
    #define Con const
    #define CI Con int&
    #define I inline
    #define W while
    #define N 600000
    #define X 998244353
    #define C(x,y) (1LL*Fac[x]*IFac[y]%X*IFac[(x)-(y)]%X)
    using namespace std;
    int n,a[N+5],Fac[2*N+5],IFac[2*N+5];
    I int QP(RI x,RI y) {RI t=1;W(y) y&1&&(t=1LL*t*x%X),x=1LL*x*x%X,y>>=1;return t;}
    I void InitFac()//预处理阶乘和阶乘逆元
    {
    	RI i;for(Fac[0]=i=1;i<=2*N;++i) Fac[i]=1LL*Fac[i-1]*i%X;
    	for(IFac[i=2*N]=QP(Fac[2*N],X-2);i;--i) IFac[i-1]=1LL*IFac[i]*i%X;
    }
    namespace FastIO
    {
    	#define FS 100000
    	#define tc() (FA==FB&&(FB=(FA=FI)+fread(FI,1,FS,stdin),FA==FB)?EOF:*FA++)
    	#define pc(c) (FC==FE&&(clear(),0),*FC++=c)
    	int OT;char oc,FI[FS],FO[FS],OS[FS],*FA=FI,*FB=FI,*FC=FO,*FE=FO+FS;
    	I void clear() {fwrite(FO,1,FC-FO,stdout),FC=FO;}
    	Tp I void read(Ty& x) {x=0;W(!isdigit(oc=tc()));W(x=(x<<3)+(x<<1)+(oc&15),isdigit(oc=tc()));}
    	Ts I void read(Ty& x,Ar&... y) {read(x),read(y...);}
    	Tp I void writeln(Ty x) {W(OS[++OT]=x%10+48,x/=10);W(OT) pc(OS[OT--]);pc('
    ');}
    }using namespace FastIO;
    struct TreeArray
    {
    	int a[N+5];I void Cl() {for(RI i=1;i<=n;++i) a[i]=0;}//清空
    	I void U(RI x) {W(x<=n) ++a[x],x+=x&-x;}I int Q(RI x,RI t=0) {W(x) t+=a[x],x-=x&-x;return t;}//单点修改;前缀查询
    }T;
    I int f(CI i,CI j) {return (C(i+j-1,j)-(j>=2?C(i+j-1,j-2):0)+X)%X;}
    int main()
    {
    	RI Tt,i,t,p,q,g,ans;read(Tt),InitFac();W(Tt--)
    	{
    		for(read(n),i=1;i<=n;++i) read(a[i]);
    		for(T.Cl(),t=n,ans=0,i=1;i<=n;T.U(a[i++]))//枚举与给定序列第一个不同位
    		{
    			if(p=T.Q(a[i]),q=n-a[i]-(i-1-p),q<t?(t=q,g=1):g=0,!t) break;//如果t=0,说明无法构造更大的排列
    			if(ans=(ans+f(n-i+1,t-1))%X,!g&&p^(a[i]-1)) break;//统计答案;如果需要加入B序列但更小的数未全被加入则结束枚举
    		}printf("%d
    ",ans);
    	}return clear(),0;
    }
    
    败得义无反顾,弱得一无是处
  • 相关阅读:
    Java 编程下的并发线程之间的同步代码块死锁
    Java 编程下的同步代码块
    Android 编程下两种方式注册广播的区别
    Java 编程下泛型的内部原理
    Android 编程下使用 Google 的 Gson 解析 Json
    c# 调用音库开发英语单词记忆本
    在.Net中实现RichClient+Restful+JPA架构探索实现
    Extjs 常用正则式
    企业内IT部门的一些问题总结
    WinServer2003部署VS2010的水晶报表
  • 原文地址:https://www.cnblogs.com/chenxiaoran666/p/Luogu4769.html
Copyright © 2011-2022 走看看