「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. Dbvisualizer 连接oracle数据库

    软件及驱动下载: 链接:https://pan.baidu.com/s/1OhuRDCd6FDi21NyCEdN2dA 密码:0rtp 软件破解办法: 1. 找到<C:\Program File ...

  2. html checkbox的checked属性问题和value属性问题

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...

  3. swift 一句代码补全tableView分割线

    1.swift实现分割线补全 swift一个大进步,只要设置tableView.separatorInset = UIEdgeInsets.zero即可补全分割线, 2.OC实现分割线补全 而在OC中 ...

  4. 3.写一个hello world页面

    经过1和2的学习,现在已经可以正常启动Django了,这一节说怎么写一个hello world页面,所有的环境基础就是1和2中搭建的 1.在app模块中添加页面 具体为 hello_django\he ...

  5. 【HTTP】HTPP学习笔记

    1.了解web及网络基础 HTTP的诞生 TCP/IP协议族 应用层 FTP文件传输协议 HTTP超文本传输协议 DNS域名系统:IP地址<--->域名 传输层 TCP传输控制协议 三次握 ...

  6. 读paper:image caption with global-local attention…

    最近的图片caption真的越来越火了,CVPR ICCV ECCV AAAI很多顶级会议都有此类的文章,今天我来讲一篇发表在AAAI的文章,因为我看了大量的论文,最近感觉AAAI越来越水了.所以这篇 ...

  7. Floyd 学习笔记

    #include <cstdio> #include <cstring> #include <ctype.h> #include <cstdlib> # ...

  8. iOS 基本数据类型 和 指针 特点

    基本数据类型 : 整型int, 字符型char , 浮点型 (float 和 double), 枚举型; -- 构造类型 : 数组类型, 结构体类型, 共用体类型; -- 指针类型 : 最终要的数据类 ...

  9. jQuery悬浮焦点图宽屏

    在线演示 本地下载

  10. jQuery自定义美化下拉框

    在线演示 本地下载