[NOI2018] 冒泡排序

CL.[NOI2018] 冒泡排序

结论1.交换次数压到下界,当且仅当不存在长度大于 2 的下降子序列。

证明很简单。众所周知的是,冒泡排序的交换次数等于序列逆序对数。要压到下界,与每个点有关的逆序对数都只能为 |ipi|,因为从 ipi 的过程中本身就要交换 |ipi| 次。如果存在长度大于 2 的下降子序列的话,则逆序对就不仅是两两间贡献了,隔着中间一个数也能贡献,因而就压不到下界。

不存在长度大于 2 的下降子序列,等价于序列可以被划分为不超过 2 条上升子序列——我们抽出所有前缀 max 构成一条上升子序列,则剩余的东西必然也递增,不然如果出现递减的两个数,从前面挑一个前缀 max 就能构成长度为 3 的下降子序列。

要求出所有字典序 >q 的排列 p 个数,常见套路是枚举它们有长度为 i1 的前缀相同,且第 ip 更大。

首先,我们可以判一下 q 的长度为 i1 的前缀是否合法:首先,要确保其本身的非前缀 max 元素递增;其次,要确保其未填入的元素全部 > 最后的非前缀 max 元素。这样的话,就可以设计DP状态:fi,j 表示有 i< 前缀 max 的元素、j> 前缀 max 的元素未填入时的方案数。则,fi,j=fi1,j+k=1jfi+k1,jk,其中前半部分表示填入一个 <max 的元素,显然为了保证递增只能填最小的一个;后半部分是枚举填入 >max 的数中第 k 大的数填入,则前 k1 个数都会被划归 <max 的数。两个转移式可以被合并为 fi,j=k=0jfi+k1,jk。到这里 80 分已经到手了,但我们还可以考虑优化。

如果在矩阵上画出来的话,会发现这转移的位置是一条斜线,不好处理;但是我们可以转变DP状态:设新的 fi,j,其中 i 是原本的 i+j(总序列长度),j 是原本的 i<max 的数的数量)。则有 fi,j=k=0ijfi1,j+k1。考虑换成枚举 j+k1,得到 fi,j=k=max(0,j1)i1fi1,k。到这里我们就发现之前贸然设的 k=0 的下界不合法,可能会出现负数,于是便重新设了 max(0,j1) 的下界。但是,更好的方式是,我们直接令 j<0fi,j=0 即可直接使用 fi,j=k=j1i1fi1,k 的转移式。

下面我们考虑用此DP值来求答案。枚举前缀长度 i,并设 j 表示后缀 [i+1,n] 中有多少个数 <max。则此部分的贡献就是 jfni,j。现在考虑有哪些 j 是合法的。设若位置 i 老老实实填上了 qi,剩余后缀中 <max 的数量是 k

发现,就算 qi 并非前缀 max,即可以填入 <max 的数时,<max 的数也不能填,因为所有未填的 <max 的数中只能填最小的,而 qi 一定不会比最小数还小,因此只能填入一个前缀最大值。故若 qi 是前缀 max,贡献为 j=k+1nifni,j;否则,即 qi 并非前缀 max,填入 i 位置的前缀 max 会自动将 qi 也归入非前缀 max 类中,故 k 还是至少会加一。所以两部分综合起来看,都是 j=k+1nifni,j

发现,无论是转移式还是求值式,我们都需要用到DP数组的后缀和。于是我们设 Sn,m 表示 i=mnfn,i

好耶!是大家daisuki的推式子时间!

Sn,m=i=mnfn,i=i=mnj=i1n1fn1,j=j=m1n1i=mj+1fn1,j=j=m1n1fn1,j+j=m1n1i=m+1j1fn1,j=Sn1,m1+i=m+1jj=i1n1fn1,j=Sn1,m1+i=m+1jfn,i=Sn1,m1+Sn,m+1

OK!这样我们就得出了简单的后缀和公式!

现在考虑其实际意义:从 S0,0=1 出发,每次要么向 n,m 正方向各走一步,要么向 m 的负方向走一步。同时,因为在合法的转移式中可能会出现 Sn,1,但不会出现更负的数,因此我们便规定不能越过 m=1 这条界线。

因为这个“不越过 1 的界线”长得很像卡特兰数模型,因此我们考虑令 m 全体增加 1 再说。于是现在 S0,0=S0,1=1(原本有 S0,1=1,现在加一得到了 S0,0=1)。两个起始状态觉得也不好做,因此我们再强制令全体 n 增加 1。于是现在 S1,0=S1,1=1。假如我们此时令 S0,0=1 的话,就会发现由上述操作刚好可以得出 S1,0=S1,1=1,因此我们这个操作是正确的。

发现只有一种操作在 n 方向上有移动不好做。考虑让后一种操作也在 n 轴正方向上走一步。因为原本方案中 n 轴上动了恰好 n 步,故 m 轴正方向实际上也动了 n 步,则 m 轴负方向实际上动了 nm 步;因为 m 轴负方向的移动现在同时在 n 轴正方向上移动,因此就多移动了 nm 步,因此终点现在是 (2nm,m)

发现现在是裸的卡特兰数模型。于是就直接得到 (2nmnm)(2nmnm1) 的卡特兰数。

因为我们之前强制令 n,m 各增加了 1,因此 Sn,m 中的 n,m 要比上式中的 n,m 各少一,所以此时要把它补回去,因此有 Sn,m=(2nm+1nm)(2nm+1nm1)

时间复杂度可以做到 O(n)。同时,在依次处理 q 的每个前缀的时候,要记得判断后缀中所有元素是否都 > 非前缀 max 元素构成的序列中的 max

代码:

#include<bits/stdc++.h>
using namespace std;
const int mod=998244353;
const int N=1200000;
int T,n,q[1201000],fac[1201000],inv[1201000],premax,secmax,res,k,sufmin[1201000];
int ksm(int x,int y=mod-2){int z=1;for(;y;y>>=1,x=1ll*x*x%mod)if(y&1)z=1ll*z*x%mod;return z;}
int binom(int x,int y){if(x<y||y<0)return 0;return 1ll*fac[x]*inv[y]%mod*inv[x-y]%mod;}
int S(int x,int y){if(y>x)return 0;return(binom(2*x-y+1,x-y)-binom(2*x-y+1,x-y-1)+mod)%mod;}
int main(){
	fac[0]=1;for(int i=1;i<=N;i++)fac[i]=1ll*fac[i-1]*i%mod;
	inv[N]=ksm(fac[N]);for(int i=N;i;i--)inv[i-1]=1ll*inv[i]*i%mod;
	scanf("%d",&T);
	while(T--){
		scanf("%d",&n),premax=secmax=res=k=0;
		for(int i=1;i<=n;i++)scanf("%d",&q[i]);sufmin[n+1]=0x3f3f3f3f;
		for(int i=n;i;i--)sufmin[i]=min(sufmin[i+1],q[i]);
		for(int i=1;i<=n;i++){
			if(secmax>sufmin[i])break;
			if(q[i]>premax)k+=q[i]-premax-1,premax=q[i];else secmax=max(secmax,q[i]),k--;
			(res+=S(n-i,k+1))%=mod;
		}
		printf("%d\n",res);		
	}
	return 0;
}
posted @   Troverld  阅读(130)  评论(0编辑  收藏  举报
编辑推荐:
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 【杂谈】分布式事务——高大上的无用知识?
点击右上角即可分享
微信分享提示