ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

P4769 [NOI2018] 冒泡排序

2022-01-02 15:01:00  阅读:178  来源: 互联网

标签:填入 int P4769 冒泡排序 text 序列 NOI2018 我们 dp


闲话:之前在做这道题的时候猜了一个结论,最长上升子序列的个数不能超过二,当时证着证着觉得假了。。。


首先一个序列如果能满足下界,那么其中的每一个元素必然是只向其目标方向移动。又由于,一个数的移动情况与其左边比其大的数的个数和右边比其小的数的个数决定,而且必然是先向左再向右,所以对于任意一个数,不能同时存在左边有数比其大,右边有数比其小。

上面这个性质,再转换一下,等价于不存在长度大于等于三的下降子序列。于是啊,题目中奇怪的需要满足冒泡排序下界的这个性质,就被我们转化为了比较优美的一个性质了。

我们暂且先不考虑题目要求的字典序严格大于,即单纯的求好排列的个数,这个我们显然可以直接 \(\text{dp}\) 。我们先来考虑一波长度大于等于三的下降子序列的性质,不妨假设此时存在一个长度大于等于三的下降子序列。

  1. 如果这个子序列的第一个数已经被我们填入了,那么其剩下部分不能出现长度大于等于二的下降子序列。
  2. 如果这个子序列的前两个数已经被我们填入了,那么其剩下部分不能存在比第二个数小的数。

所以说,我们便需要设计一种状态,使得其满足我们列出的两个性质。不妨令 \(f_{i,j}\) 表示已经填入 \(i\) 个数,且其中最大的数是 \(j\) ,且满足不存在没填入的数比已经填入的数的长度为 \(2\) 的下降子序列的后一个数小。我们假设当前填入的数为 \(k\) ,那么此时分两种情况:

  1. \(k<j\) ,那么我们便不会更新 \(j\) ,即 \(f_{i+1,j}\leftarrow f_{i,j}\) 。但是此时需要满足剩下的数不存在比 \(k\) 小的,即 \(k\) 是没填入的数中最小的。
  2. \(k>j\) ,那么我们便会更新 \(j\) ,即 \(f_{i+1,k}\leftarrow f_{i,j}\) 。

可以发现,两者的转移都是唯一的。但是,我们需要判断是否存在上面两种情况的转移,可以证明,除当 \(i=j\) 时第一种情况不能转移外,其余情况均能转移。

注:这种 \(\text{dp}\) 的设计方法还是很有启发性的,虽然说一般 \(\text{dp}\) 都是这么搞的,但是感觉条理分明地去分析我们需要 \(\text{dp}\) 的对象的性质,并将其加入我们的状态设计中,还是需要一定的锻炼的。

这个 \(\text{dp}\) 直接暴力做是 \(O(n^2)\) 的,不太行,但是手搓可以发现,这个转移等价于卡特兰数的转移,所以我们就得到了 \(O(n)\) 的解法了。

我们此时便再来考虑一下字典序的情况。字典序的比较实际上是只需要比较 \(\text{lcp}\) 的下一位的大小即可,所以我们就存在了一个想法,即在前 \(i-1\) 位填入和 \(q\) 一样的数,并在 \(i\) 填入大于 \(q_i\) 的数,我们便能得到大于其字典序的排列了。易发现,我们这个操作是很容易与我们前面的 \(\text{dp}\) 过程结合在一起的。

具体的,我们令 \(p_i=\max_{j=1}^{i}q_j\) ,那么对于在第 \(i\) 个位置能得到大小关系的排列,实际上就是在 \(f_{i-1,p_{i}+1}\) 的位置贡献了 \(1\) ,我们考虑快速计算这个位置的 \(1\) 对于 \(f_{n,n}\) 的贡献,还是利用类似卡特兰数的做法,我们令总方案减去跨过中轴的方案即可,即 \(\binom{2n-i-p_{i}}{n-i+1}-\binom{2n-i-p_{i}}{n-i+2}\) 。

应该就做完了?记得判断这个 \(q_i\) 的前缀是否合法,这里可以使用链表来判断每次塞入的数是否是最小的,这样总复杂度就是 \(O(n)\) 的。

#include<bits/stdc++.h>
using namespace std;
const int N=6e5+5;
const int MOD=998244353;
int ADD(int x,int y){return x+y>=MOD?x+y-MOD:x+y;}
int SUB(int x,int y){return x-y<0?x-y+MOD:x-y;}
int TIME(int x,int y){return (int)(1ll*x*y%MOD);}
int ksm(int x,int k=MOD-2){int res=1;for(;k;k>>=1,x=TIME(x,x))if(k&1)res=TIME(res,x);return res;}
int fact[N<<1],ifact[N<<1];
int C(int n,int m){
	if(n<0||m<0||n-m<0) return 0;
	return TIME(fact[n],TIME(ifact[m],ifact[n-m]));
}
int n,q[N],p[N],L[N],R[N],res;
void DEL(int x){
	R[L[x]]=R[x],L[R[x]]=L[x];
}
int solve(){
	cin>>n,R[0]=1,L[n+1]=n,res=0;
	for(int i=1;i<=n;++i) L[i]=i-1,R[i]=i+1;
	for(int i=1;i<=n;++i) scanf("%d",&q[i]);
	for(int i=1;i<=n;++i) p[i]=max(p[i-1],q[i]);
	for(int i=1;i<=n;++i){
		res=ADD(res,SUB(C(2*n-i-p[i],n-i+1),C(2*n-i-p[i],n-i+2)));
		if(q[i]<p[i]&&R[0]!=q[i]) break;else DEL(q[i]);
	}
	return printf("%d\n",res),0;
}
int main(){
	fact[0]=1;
	for(int i=1;i<(N<<1);++i) fact[i]=TIME(fact[i-1],i);
	ifact[(N<<1)-1]=ksm(fact[(N<<1)-1]);
	for(int i=(N<<1)-1;i>=1;--i) ifact[i-1]=TIME(ifact[i],i);
	int T;cin>>T;while(T--) solve();
	return 0;
}

标签:填入,int,P4769,冒泡排序,text,序列,NOI2018,我们,dp
来源: https://www.cnblogs.com/Point-King/p/15757486.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有