【NOI 2018】冒泡排序(组合数学)
题意大概是给定一个长度为$n$的排列$p$,求有多少长度为$n$的排列满足冒泡排序的交换次数为$\frac{1}{2} \sum\limits_{i = 1}^{n}|i - p_{i}|$。
可以发现,该式子是冒泡排序复杂度的下界,任意一个数想要回到规定的位置至少要被交换$|i - p_{i}|$次,即在排序过程中不浪费任何一次交换,每一个数都只能向它归回原位的方向走。
稍加思索,可以得出一个结论:
- 任何一个最长下降子序列长度超过$2$的排列一定是不合法的。
- 任何一个最长下降子序列不超过$2$的排列一定合法。
考虑第一句话的正确性:
试想处于中间的那个数,左边存在比它大的数,右边存在比它小的数,那么该数一定会被向左交换一次,也一定会被向右交换一次,然后这两次交换是对该数无意义的,因为它向两个方向走了,导致位置没有改变。所以这两次的交换被浪费,故一定不合法。
考虑第二句话的正确性:
试想一个逆序对,处于右边小的数一定会向左走,如果要使它不合法,必定要令它向右至少走一步,那就意味着右边存在更小的数,与原条件矛盾,对于左边的数同理。
有了这个结论,不考虑起始字典序的限制,我们就可以写出一个$O(n^{2})$的dp,考虑$f_{i,j}$表示前$i$数,记$i$个数中最大的数为$mx$,剩余数中有$j$个数比$mx$小,此时的方案数。
此处有两种转移:
- 选一个比$mx$大的数,不会破坏上述结论。
- 选剩余几个比$mx$小的数中最小的那个。如果出现了长度为$3$的最长下降子序列,那在它第二个数的时候就不会被选了,因为它不是最小的数。在$j = 0$时不能用此转移。
这样就能$O(n^{2})$做一个dp了。我们把dp的两维看作二维坐标,看看它的实际意义。
我们每次横坐标$i$加$1$时,纵坐标$j$每次加一个非负整数,或者减一。其中$j$总是非负的,那由$(0,0)$走到$(n,0)$的一条合法路径就是一个合法答案。
由于每次增加的是任意非负整数,不好计算,我们甚至可以把它转化成括号序列。每次横坐标增加$1$个单位时,相当于加了一个右括号,在这之前可以加任意多个左括号,对应了纵坐标增加量加一,即将$j-1$对应了不加左括号。显然这个模型和上一个完全一样,而且你可以快速的用组合数算出括号序列的总数。
最后我们来考虑初始字典序的限制,通过上述论证,我们知道一个字典序对应了一个括号序列,我们考虑在哪一个位置突破了字典序的限制,那么显然以后就可以随机游走了。突破字典序的限制,意味着选一个比当前更大的数(一定会更大,如果选择了一个比$mx$小但比当前数大的一个数就会违反只能选最小的数的规则),我们只要比原先多加一个左括号就可以了(不需要多加,因为那会被随机游走枚举到)。
此处有一个小trick:我们在计算栈中还有$x$个左括号,还有$y$个右括号将来匹配的括号序列方案数时,组合数算出来的是随机游走的方案数,事实上我们必须保证纵坐标非负,我们只需要容斥掉不合法的就可以了。具体来讲就是将我们要算的起点按直线$y=-1$镜像,再计算没有右括号数限制的随机游走的方案数,因为每一个算出来的方案一定会经过$y=-1$这条直线,我们把这个方案表示的路径在第一次触碰直线$y=-1$之前的那段向上翻回去,就能对应了原起点在游走时被计算进去的一条不合法路径,把这个减掉就可以了。
所以总的时间复杂度是$O(n)$的。
$\bigodot$技巧&套路:
- 冒泡排序的复杂度(交换次数)分析。
- 随机游走和括号序列计数的联系。
- 有限制的括号序列计数的容斥转化技巧。
#include <cstdio>
#include <cstring>
#include <algorithm> typedef long long LL;
const int N = , MOD = ; int tc, n;
int fac[N], ifac[N], p[N >> ], vis[N >> ]; inline void Read(int &x) {
x = ; static char c;
for (c = getchar(); c < '' || c > ''; c = getchar());
for (; c >= '' && c <= ''; x = (x << ) + (x << ) + c - '', c = getchar());
} inline int Pow(int x, int b) {
static int re;
for (re = ; b; b >>= , x = (LL) x * x % MOD)
if (b & ) re = (LL) re * x % MOD;
return re;
} inline int C(int x, int y) {
if (x < y) return ;
return (LL) fac[x] * ifac[y] % MOD * ifac[x - y] % MOD;
}
inline int Cal(int x, int y) {
if (x < y) return ;
int dis = * x - y, re1 = C(dis, x);
if (y + > dis) return re1;
return (re1 - C(dis, (dis + y) / + )) % MOD;
} int main() {
freopen("inverse.in", "r", stdin);
freopen("inverse.out", "w", stdout); fac[] = ;
for (int i = ; i < N; ++i) {
fac[i] = (LL) fac[i - ] * i % MOD;
}
ifac[N - ] = Pow(fac[N - ], MOD - );
for (int i = N - ; i >= ; --i) {
ifac[i - ] = (LL) ifac[i] * i % MOD;
} scanf("%d", &tc);
for (; tc; --tc) {
int re = , mx = , low = , he = ;
memset(vis, , sizeof vis);
scanf("%d", &n);
for (int i = ; i <= n; ++i) {
Read(p[i]);
}
for (int i = ; i < n; ++i, --he) {
if (mx < p[i]) he += p[i] - mx;
mx = std::max(mx, p[i]);
for (vis[p[i]] = ; vis[low]; ++low);
re = (re + Cal(n - i + , he + )) % MOD;
if (mx > p[i] && p[i] > low) {
break;
}
}
printf("%d\n", (re + MOD) % MOD);
} return ;
}
【NOI 2018】冒泡排序(组合数学)的更多相关文章
- [NOI 2018]冒泡排序
题意:求所有字典序大于给定序列且满足条件的排列个数之和. 思路: 考虑dp即可,打表出卡特兰数优化,直接dp可以44... #include <bits/stdc++.h> using n ...
- [LOJ 2720][BZOJ 5417][UOJ 395][NOI 2018]你的名字
[LOJ 2720][BZOJ 5417][UOJ 395][NOI 2018]你的名字 题意 给定一个大串 \(S\) 以及 \(q\) 次询问, 每次询问给定一个串 \(T\) 和区间 \([l, ...
- NOI 2018 酱油记
转眼离 NOI 2018 已经过了一个星期了,退役的我还是随便来水水吧. 语法.错字之类的可能会很多,但是我也不拘这点小节了. 恭喜 yww, zjt, sk 进队,zwl, myh au , yay ...
- [LOJ 2718][UOJ 393][BZOJ 5415][NOI 2018]归程
[LOJ 2718][UOJ 393][BZOJ 5415][NOI 2018]归程 题意 给定一张无向图, 每条边有一个距离和一个高度. 再给定 \(q\) 组可能在线的询问, 每组询问给定一个点 ...
- [LOJ 2721][UOJ 396][BZOJ 5418][NOI 2018]屠龙勇士
[LOJ 2721][UOJ 396][BZOJ 5418][NOI 2018]屠龙勇士 题意 题面好啰嗦啊直接粘LOJ题面好了 小 D 最近在网上发现了一款小游戏.游戏的规则如下: 游戏的目标是按照 ...
- NOI 2018网络同步赛(游记?)
刚中考完那段时间比较无聊,报名了一个同步赛,报完名才发现成绩单是要挂到网上的,而且因为报的早给了一个很靠前的考号...那布星啊,赶紧学点东西,于是在一周内学了网络流,Treap以及一些数论. Day1 ...
- [NOI 2018] 归程
Description 传送门 Solution 65分做法 先求出每个点到\(1\)号点的最短路,记为\(d[i]\).然后按照海拔从大到小依次加边,并查集维护每个连通块中\(d[i]\)的最小值, ...
- 解题:NOI 2018 归程
题面 清新友好的题目 跑一个最短路,然后对海拔建Kruskal重构树,从最后接上去的边(最低的一个)开始DFS一下处理子树里路程的最小值. 询问是每次在重构树上倍增找到深度最浅的海拔高于当天水位线的节 ...
- 【NOI 2018】屠龙勇士(扩欧)
题意理解错了... 一把剑打一条龙,打了$x$次后如果龙不死,你就Game Over了. 显然,面对每条龙使用的剑是固定的,如果所有龙中有一条没打死你就挂了. 可以知道,可行的答案集合就是所有龙的可行 ...
随机推荐
- 【SIKIA计划】_03_C#初级教程 (2015版)笔记
Win32 API是微软的操作系统Windows提供给开发人员的编程接口,它决定了我们开发的Windows应用程序的能力.MFC是微软为开发人员提供的类库,在某种意义上是对Win32 API的封装.M ...
- 测试目录结构Aegisub【自用笔记】
手册链接https://aegi.vmoe.info/docs/3.2/Main_Page/ 01,加粗 b标签 1开启 其他或不写则关闭 买{\b1}苹果{\b0}123 02,倾斜 i标签 ...
- RAID系列技术详解
1.RAID 0 RAID 0是把n个物理磁盘虚拟成一个逻辑磁盘,即形成RAID 0的各个物理磁盘会组成一个逻辑上连续,物理上也连续的虚拟磁盘.一级磁盘控制器(指使用这个虚拟磁盘的控制器,如果某台主机 ...
- 使用vbox构建局域网络
update: 也可以启用DHCP自动分配IP地址.(看到过的某一篇博文写过要使用这个服务还得自己搭--就没有动手去实践一下直接手动分配了静态的IP.偶然尝试了一下发现动态IP分配和手动静态IP分配都 ...
- xlutils模块使用
python常用模块目录 1.xlutils 实现拷贝原文件 原表格: import xlrd from xlutils.copy import copy workbook = xlrd.open_w ...
- mv命令详解
基础命令学习目录首页 原文链接:https://www.cnblogs.com/piaozhe116/p/6084214.html mv命令是move的缩写,可以用来移动文件或者将文件改名(move ...
- gzip命令详解
基础命令学习目录首页 好文链接:https://blog.csdn.net/m0_38132420/article/details/78577247 原文链接:http://www.cnblogs.c ...
- textarea拖拽控制
一.用处 textarea默认时允许用户以拖拽形式来改变textarea大小,但textarea的大小变化会撑大其父节点,有时会破坏整体布局,有时我们并不希望textarea随意拖拽. forklif ...
- Grunt 5分钟上手:合并+压缩前端代码
Grunt 的各种优点这里就不扯了,对于 新手来说 合并(concat) + 压缩(uglify) 前端代码的需求量应该是最大的,这里以这俩种功能为主做一个5分钟的入门吧! 工作环境 $ node - ...
- NEWBEE软件团队 人员分配情况及分数获得方式
人员分配: PM:李桐 王骜 dev:王骜 刘垚鹏 安康 林旭鹏 黄新越 test:黄伟龙 李桐 马佐霖 黄新越 注:黄新越为女生,不方便平时的交流,所以任务分配较为灵活,特分在两个组里. 评分 ...