「NOI2018」冒泡排序

题目描述

最近,小S 对冒泡排序产生了浓厚的兴趣。为了问题简单,小 S 只研究对 1 到n
的排列的冒泡排序。

下面是对冒泡排序的算法描述。

输入:一个长度为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] 的值

冒泡排序的交换次数被定义为交换过程的执行次数。可以证明交换次数的一个下
界是$\frac{1}{2} \sum_{i=1}^n |i-p_i|$其中$p_i$ 是排列p 中第i 个位置的数字。如果你对证明感兴趣,可以看提示。

小S 开始专注于研究长度为n 的排列中,满足交换次数= $\frac{1}{2} \sum_{i=1}^n |i-p_i|$的排列
(在后文中,为了方便,我们把所有这样的排列叫“好”的排列)。他进一步想,这样的排列到底多不多?它们分布的密不密集?
小S 想要对于一个给定的长度为n 的排列q,计算字典序严格大于q 的“好”的
排列个数。但是他不会做,于是求助于你,希望你帮他解决这个问题,考虑到答案可能会很大,因此只需输出答案对998244353 取模的结果。

输入输出格式

输入格式:

从文件inverse.in 中读入数据。

输入第一行包含一个正整数T,表示数据组数。

对于每组数据,第一行有一个正整数n, 保证$n \leq 6 \times 10^5$。

接下来一行会输入n 个正整数,对应于题目描述中的qi,保证输入的是一个1 到
n 的排列。

输出格式:

输出到文件inverse.out 中。

输出共T 行,每行一个整数。

对于每组数据,输出一个整数,表示字典序严格大于q 的“好”的排列个数对
998244353 取模的结果。

输入输出样例

输入样例#1:
复制

1
3
1 3 2
输出样例#1:
复制

3
输入样例#2:
复制

1
4
1 4 2 3
输出样例#2:
复制

9

说明

下面是对本题每个测试点的输入规模的说明。

对于所有数据,均满足T = 5 (样例可能不满足).

记$n_{max}$ 表示每组数据中n 的最大值,

$\sum n$ 表示所有数据的n 的和。

测试点 $n_{max}=$ $\sum n\leq$ 特殊性质 测试点 $n_{max}=$ $\sum n\leq$ 特殊性质
1 $8$ $5n_{max}$ 13 $144$ $700$
2 $9$ $5n_{max}$ 14 $166$ $700$
3 $10$ $5n_{max}$ 15 $200$ $700$
4 $12$ $5n_{max}$ 16 $233$ $700$
5 $13$ $5n_{max}$ 17 $777$ $4000$ $\forall i ~~p_i=i$
6 $14$ $5n_{max}$ 18 $888$ $4000$
7 $16$ $5n_{max}$ 19 $933$ $4000$
8 $16$ $5n_{max}$ 20 $1000$ $4000$
9 $17$ $5n_{max}$ 21 $266666$ $2000000$ $\forall i ~~p_i=i$
10 $18$ $5n_{max}$ 22 $333333$ $2000000$
11 $18$ $5n_{max}$ 23 $444444$ $2000000$
12 $122$ $700$ $\forall i ~~p_i=i$ 24 $555555$ $2000000$
. . . . 25 $600000$ $2000000$

下面是对交换次数下界是$\frac{1}{2} \sum_{i=1}^n |i-p_i|$的证明。

排序本质上就是数字的移动,因此排序的交换次数应当可以用数字移动的总距离
来描述。对于第i 个位置,假设在初始排列中,这个位置上的数字是$p_i$,那么我们需要将这个数字移动到第pi 个位置上,移动的距离是$|i-p_i|$。从而移动的总距离就是$\sum_{i=1}^n |i-p_i|$,而冒泡排序每次会交换两个相邻的数字,每次交换可以使移动的总距离至多减少2。因此$\frac{1}{2} \sum_{i=1}^n |i-p_i|$是冒泡排序的交换次数的下界。

并不是所有的排列都达到了下界,比如在n = 3 的时候,考虑排列3 2 1, 这个排
列进行冒泡排序以后的交换次数是3,但是$\frac{1}{2} \sum_{i=1}^n |i-p_i|$ 只有2。

【样例1 解释】
字典序比1 3 2 大的排列中,除了3 2 1 以外都是“好”的排列,故答案为3。

题解

参照liuzhangfeiabc的题解

在NOI考场上是一道绝好的打表找规律题,打表观察性质。

题目可以转化为:要求排列中不存在长度\(\ge 3\)的下降子序列。

因为如果出现的话,那么这个下降子序列中间的元素需要先与左边比它大的元素交换再与右边比它小的元素交换,需要折返一下,显然就不合法了。(这一步说明题面里的提示不是没用的)

这又等价于可以将序列划分为\(2\)个上升子序列。

首先我们先不看那个字典序的性质,相信大家打一下表就能发现答案是卡特兰数。然后我们再想想它的本质是什么:

假设前\(i\)个位置中,最大的数是\(j\),那么我们会发现,\(>j\)的数目前是可以随便填的,然而\(<j\)的数只能限制从小到大按顺序填入(因为这些元素一定被归入同一个上升子序列)。

于是我们就可以设\(f(i,j)\)表示还剩余\(i\)个数没填,其中后\(j\)个是大于当前最大值的“非限制元素”的方案数。

转移就是枚举下一个位置填一个限制元素或某一个非限制元素。

如果填限制元素,非限制元素的数量不变;否则假设填入第\(k\)个非限制元素,非限制元素的数量就会减少\(k\)个(这是因为最大值发生了变化,使得前面\(k-1\)个非限制元素变成了限制元素)。

\[f(i,j) = \sum_{k=0}^j f(i-1,j-k)
\]

边界是\(f(0,0) = 1\)。当然在这里\(f(i,i)\),就是非限制元素个数\(i\)等于剩余元素个数\(i\)的时候的转移不对,但先默认不合法的转移的方案数为\(0\)。

这其实就是个前缀和:

\[f(i,j) = f(i,j-1) + f(i-1,j)
\]

擅长打表熟悉组合数的人会很快发现,这东西就是两个组合数相减:

\[f(i,j) = \binom{i+j-1}{j} - \binom{i+j-1}{j-2}
\]

它的正确性容易用归纳法验证:

\[f(i,j-1) + f(i-1,j) = \binom{i+j-2}{j-1} + \binom{i+j-2}{j} - \binom{i+j-2}{j-3} - \binom{i+j-2}{j-2} = \binom{i+j-1}{j} - \binom{i+j-1}{j-2} = f(i,j)
\]

特别地,\(f(i,0) = 1,f(i,1) = i\)。这里的组合数可以保证不合法的转移是\(0\),也就是说我们用不严谨的递推公式总结出的组合数公式是对的。

这也解释了为什么没有限制时答案是卡特兰数:只需注意到此时的所求是\(f(n,n)\)

\[f(n,n) = \binom{2×n-1}{n} - \binom{2×n-1}{n-2} = C_n
\]

我们再考虑限制,假设当前做到第\(i\)位,给定的排列中这一位是\(a_i\),后面有\(suf_i\)个数比它大,前面有\(pre_i\)个数比它小(这两个数组可以用树状数组方便地计算出来)并且现在的“非限制元素”还有\(nw\)个。然后类似数位DP那样逐位进行。

  1. 直接退出的情况:

    \(suf_i=0\),这表明\(a_i\)是最大的数,这个位置没得选,并且后面没填的数只能按顺序填入,而这个排列的字典序是严格不大于给定排列的,因此就可以退出了。
  2. 填入的数字\(p_i>a_i\)的情况:

    首先\(nw\)可以与\(suf_i\)取个\(\min\),因为填完这一位后非限制元素一定不超过\(suf_i\)个。

    然后我们相当于要求\(\sum_{j=0}^{nw-1} f(n-i,j)\),根据前缀和它等于\(f(n-i+1,nw-1)\),可以\(O(1)\)计算。
  3. 令\(p_i=a_i\),考虑填入\(p_i=a_i\)是否合法:

    如果刚刚\(suf_i\)更新了\(nw\),说明\(a_i\)本身就是一个“非限制元素”,当然合法;

    否则,如果\(a_i\)是当前未填入的元素中最小的(对应\(pre_i=a_i-1\)),相当于填了一个最小的“限制元素”,也是合法的;

    否则,就是乱序填入“限制元素”,不合法,就可以退出了。

总复杂度\(O(n\log n)\),瓶颈其实在于树状数组求\(pre_i\)和\(suf_i\)的部分。

#include<bits/stdc++.h>
#define rg register
#define il inline
#define co const
template<class T>il T read(){
rg T data=0,w=1;rg char ch=getchar();
for(;!isdigit(ch);ch=getchar())if(ch=='-') w=-w;
for(;isdigit(ch);ch=getchar()) data=data*10+ch-'0';
return data*w;
}
template<class T>il T read(rg T&x) {return x=read<T>();}
typedef long long ll;
using namespace std; co int mod=998244353;
il int add(int x,int y){
return (x+=y)>=mod?x-mod:x;
}
il int mul(int x,int y){
return (ll)x*y%mod;
}
int fpow(int x,int k){
int ans=1;
for(;k;k>>=1,x=mul(x,x))
if(k&1) ans=mul(ans,x);
return ans;
} co int N=12e5;
int fac[N],ifac[N];
il int binom(int n,int m){
return mul(fac[n],mul(ifac[m],ifac[n-m]));
}
il int f(int q,int w){
if(w==0) return 1;
if(w==1) return q;
return add(binom(q+w-1,w),mod-binom(q+w-1,w-2));
} int n,a[N];
int val[N],suf[N],pre[N];
void add(int p){
for(int i=p;i<=n;i+=i&-i) ++val[i];
}
int query(int p){
int ans=0;
for(int i=p;i;i-=i&-i) ans+=val[i];
return ans;
} void inverse(){
read(n);
for(int i=1;i<=n;++i) read(a[i]),val[i]=0;
for(int i=n;i;--i){
suf[i]=n-i-query(a[i]);
add(a[i]);
pre[i]=i-1-(n-a[i]-suf[i]);
}
int nw=n,ans=0;
for(int i=1;i<=n;++i){
if(suf[i]==0) break;
bool flag=suf[i]<nw;
nw=min(nw,suf[i]);
ans=add(ans,f(n-i+1,nw-1));
if(!flag&&pre[i]!=a[i]-1) break;
}
printf("%d\n",ans);
}
int main(){
freopen("inverse.in","r",stdin),freopen("inverse.out","w",stdout);
fac[0]=1;
for(int i=1;i<N;++i) fac[i]=mul(fac[i-1],i);
ifac[N-1]=fpow(fac[N-1],mod-2);
for(int i=N-2;i>=0;--i) ifac[i]=mul(ifac[i+1],i+1);
for(int t=read<int>();t--;) inverse();
return 0;
}

LOJ2719 「NOI2018」冒泡排序的更多相关文章

  1. LOJ2719. 「NOI2018」冒泡排序 [组合计数]

    LOJ 思路 这题我看着题解还搞了几个小时?我也不知道自己在干啥-- 首先你要通过出色的分析能力得到一个结论:一个排列合法当且仅当它的最长下降子序列长度不超过2. 证明?懒得写了. 然后我们不管字典序 ...

  2. Loj #2719. 「NOI2018」冒泡排序

    Loj #2719. 「NOI2018」冒泡排序 题目描述 最近,小 S 对冒泡排序产生了浓厚的兴趣.为了问题简单,小 S 只研究对 *\(1\) 到 \(n\) 的排列*的冒泡排序. 下面是对冒泡排 ...

  3. 「NOI2018」冒泡排序

    「NOI2018」冒泡排序 考虑冒泡排序中一个位置上的数向左移动的步数 \(Lstep\) 为左边比它大的数的个数,向右移动的步数 \(Rstep\) 为右边比它大的数的个数,如果 \(Lstep,R ...

  4. LOJ #2719. 「NOI2018」冒泡排序(组合数 + 树状数组)

    题意 给你一个长为 \(n\) 的排列 \(p\) ,问你有多少个等长的排列满足 字典序比 \(p\) 大 : 它进行冒泡排序所需要交换的次数可以取到下界,也就是令第 \(i\) 个数为 \(a_i\ ...

  5. loj 2719 「NOI2018」冒泡排序 - 组合数学

    题目传送门 传送门 题目大意 (相信大家都知道) 显然要考虑一个排列$p$合法的充要条件. 考虑这样一个构造$p$的过程.设排列$p^{-1}_{i}$满足$p_{p^{-1}_i} = i$. 初始 ...

  6. LOJ 2719 「NOI2018」冒泡排序——模型转化

    题目:https://loj.ac/problem/2719 首先要发现合法的充要条件是 | LDS | <=2 ! 因为有没用的步数,说明一个元素先往左移.又往右移(不会先往右移再往左移,因为 ...

  7. 「NOI2018」屠龙勇士(EXCRT)

    「NOI2018」屠龙勇士(EXCRT) 终于把传说中 \(NOI2018D2\) 的签到题写掉了... 开始我还没读懂题目...而且这题细节巨麻烦...(可能对我而言) 首先我们要转换一下,每次的 ...

  8. LOJ #2721. 「NOI2018」屠龙勇士(set + exgcd)

    题意 LOJ #2721. 「NOI2018」屠龙勇士 题解 首先假设每条龙都可以打死,每次拿到的剑攻击力为 \(ATK\) . 这个需要支持每次插入一个数,查找比一个 \(\le\) 数最大的数(或 ...

  9. 「NOI2018」你的名字

    「NOI2018」你的名字 题目描述 小A 被选为了\(ION2018\) 的出题人,他精心准备了一道质量十分高的题目,且已经 把除了题目命名以外的工作都做好了. 由于\(ION\) 已经举办了很多届 ...

随机推荐

  1. static全局变量与普通的全局变量有什么区别?static局部变量和普通局部变量有什么区别?static函数与普通函数有什么区别?

    答案:全局变量(外部变量)的说明之前再冠以static 就构成了静态的全局变量.全局变量本身就是静态存储方式,静态全局变量当然也是静态存储方式. 这两者在存储方式上并无不同.这两者的区别虽在于非静态全 ...

  2. python 基础 9.6 设计表结构

    一. 设计表结构    在操作设计数据库之前,我们先要设计数据库表结构,我们就来分析分析经典的学生,课程,成绩,老师这几者他们之间的关系,我们先来分析各个主体他们直接有什么属性,并确定表结构,在实际开 ...

  3. 使用Socket通信实现FTP客户端程序

    FTP 客户端如 FlashFXP,File Zilla 被广泛应用,原理上都是用底层的 Socket 来实现.FTP 客户端与服务器端进行数据交换必须建立两个套接字,一个作为命令通道,一个作为数据通 ...

  4. hdu4063(圆与圆交+线段与圆交+最短路)

    写几何题总是提心吊胆.精度问题真心吓人. 其实思路挺简单的一道题,真是什么算法和几何double搞到一块,心里就虚虚的. 思路:求出所有圆之间的交点,然后用这些交点跑一遍最短路就可以了. Aircra ...

  5. 做完task1-21的阶段总结

    [说明]这是自注册修真院的第七天,也是第七篇日报,觉得是一个好的时机总结一下. 因为任务一虽然看起来仅仅是“完成学员报名的DB设计并读写数据库”,但是做了几天之后就发现在任务“搭建自己的服务器”之前的 ...

  6. 【BZOJ3779】重组病毒 LCT+DFS序

    [BZOJ3779]重组病毒 Description 黑客们通过对已有的病毒反编译,将许多不同的病毒重组,并重新编译出了新型的重组病毒.这种病毒的繁殖和变异能力极强.为了阻止这种病毒传播,某安全机构策 ...

  7. 【BZOJ4894】天赋 有向图生成树计数

    [BZOJ4894]天赋 Description 小明有许多潜在的天赋,他希望学习这些天赋来变得更强.正如许多游戏中一样,小明也有n种潜在的天赋,但有一些天赋必须是要有前置天赋才能够学习得到的.也就是 ...

  8. hdu 4068 I-number【大数】

    题目: http://acm.hdu.edu.cn/showproblem.php?pid=4608 http://acm.hust.edu.cn/vjudge/contest/view.action ...

  9. spring配置中的classpath

    1 classpath指WEB-INF下面的classes目录 2 配置成classpath*的话,spring会去所有的classpath中去找,包括lib下面的jar包 对于web app而言,c ...

  10. 程序运行之ELF 符号表

    当一个工程中有多个文件的时候,链接的本质就是要把多个不同的目标文件相互粘到一起.就想玩具积木一样整合成一个整体.为了使不同的目标文件之间能够相互粘合,这些目标文件之间必须要有固定的规则才行.比如目标文 ...