loj 2719 「NOI2018」冒泡排序 - 组合数学
显然要考虑一个排列$p$合法的充要条件。
考虑这样一个构造$p$的过程。设排列$p^{-1}_{i}$满足$p_{p^{-1}_i} = i$。
- 初始令$q = (1, 2, \cdots, n)$。
- 依次考虑$i = 1, 2, \cdots, n$。
- 设$x = p_i$,如果$q^{-1}_x > i$,那么交换$q_x, q_{x - 1}$。
上述算法每次交换的时候会使逆序对增加1。
考虑给出的下界,假设交换的是$i$和$i + 1$。
不难用归纳法证明$p_i \leqslant i$。
那么考虑$ \Delta = (i + 1 - p_i + |p_{i + 1} - i|) - (i - p_i + |p_{i + 1} - i - 1|)$。
- 如果$p_{i + 1} \geqslant i + 1$,那么有$ \Delta = (i + 1 - p_i + p_{i + 1} - i) - (i - p_i + p_{i + 1} - i - 1) =2$
- 如果$p_{i + 1} \leqslant i$,那么有$\Delta = (i + 1 - p_i + i - p_{i + 1}) - (i - p_i + i + 1 - p_{i + 1}) = 0$
每次改变量要么为0,要么为2,如果某一次为0,那么将永远达不到下界。
因此序列合法当仅当上述算法中,每次交换满足$q_x \geqslant x$。
上述算法中,未确定的数并且可以向前移动的是一段后缀,并且满足$q_x = x$。
假如某次将$y$向前移动,那么如果一个$z < y$,并且$z$未确定,那么你不能将$z$向前移动。
然后考虑一下没有字典序限制怎么做,显然这个问题不会更难。
设$f_{i, j}$表示考虑到排列的前$i$个数,其中最大值为$j$。
转移考虑最大值有没有发生改变。
$(i, j)$是平面上的一个点,考虑把这个问题转化到平面上。
最大值改变等于可以向上走若干步,不变相当于向右走一步。
另外还需要满足$i \geqslant j$。
用折线法可以轻松计算出方案数。
然后我们来考虑原问题。
字典序严格大于似乎有点烦?考虑小于等于。(其实是我今天想的时候把题意记错了,写完发现过不了样例)
仍然考虑枚举一个长度为$i - 1$的前缀,然后计算在$i$脱离限制后的方案数。
下面只考虑长度为$i - 1$的前缀是合法的情况。
- 如果$a_{i}$是一个前缀最大值,那么考虑$i - 1$的前缀最大值是$mx$,答案加上从$(i, mx), (i, mx + 1), \cdots, (i, a_i - 1)$开始的方案数。
- 如果$a_i$不是前缀最大值
- 如果比不是前缀最大值的最小值还大,那么此时前缀$i$不合法,答案加上从$(i, mx)$开始的方案书。
- 否则对答案没有贡献。
Code
- /**
- * loj
- * Problem#2719
- * Accepted
- * Time: 652ms
- * Memory: 10236k
- */
- #include <bits/stdc++.h>
- using namespace std;
- typedef bool boolean;
- #define ll long long
- void exgcd(int a, int b, int& x, int& y) {
- if (!b) {
- x = 1, y = 0;
- } else {
- exgcd(b, a % b, y, x);
- y -= (a / b) * x;
- }
- }
- int inv(int a, int n) {
- int x, y;
- exgcd(a, n, x, y);
- return (x < 0) ? (x + n) : (x);
- }
- const int Mod = 998244353;
- template <const int Mod = :: Mod>
- class Z {
- public:
- int v;
- Z() : v(0) { }
- Z(int x) : v(x){ }
- Z(ll x) : v(x % Mod) { }
- friend Z operator + (const Z& a, const Z& b) {
- int x;
- return Z(((x = a.v + b.v) >= Mod) ? (x - Mod) : (x));
- }
- friend Z operator - (const Z& a, const Z& b) {
- int x;
- return Z(((x = a.v - b.v) < 0) ? (x + Mod) : (x));
- }
- friend Z operator * (const Z& a, const Z& b) {
- return Z(a.v * 1ll * b.v);
- }
- friend Z operator ~(const Z& a) {
- return inv(a.v, Mod);
- }
- friend Z operator - (const Z& a) {
- return Z(0) - a;
- }
- Z& operator += (Z b) {
- return *this = *this + b;
- }
- Z& operator -= (Z b) {
- return *this = *this - b;
- }
- Z& operator *= (Z b) {
- return *this = *this * b;
- }
- friend boolean operator == (const Z& a, const Z& b) {
- return a.v == b.v;
- }
- };
- Z<> qpow(Z<> a, int p) {
- Z<> rt = Z<>(1), pa = a;
- for ( ; p; p >>= 1, pa = pa * pa) {
- if (p & 1) {
- rt = rt * pa;
- }
- }
- return rt;
- }
- typedef Z<> Zi;
- typedef class Input {
- protected:
- const static int limit = 65536;
- FILE* file;
- int ss, st;
- char buf[limit];
- public:
- Input():file(NULL) { };
- Input(FILE* file):file(file) { }
- void open(FILE *file) {
- this->file = file;
- }
- void open(const char* filename) {
- file = fopen(filename, "r");
- }
- char pick() {
- if (ss == st)
- st = fread(buf, 1, limit, file), ss = 0;//, cerr << "str: " << buf << "ed " << st << endl;
- return buf[ss++];
- }
- } Input;
- #define digit(_x) ((_x) >= '0' && (_x) <= '9')
- Input& operator >> (Input& in, unsigned& u) {
- char x;
- while (~(x = in.pick()) && !digit(x));
- for (u = x - '0'; ~(x = in.pick()) && digit(x); u = u * 10 + x - '0');
- return in;
- }
- Input& operator >> (Input& in, unsigned long long& u) {
- char x;
- while (~(x = in.pick()) && !digit(x));
- for (u = x - '0'; ~(x = in.pick()) && digit(x); u = u * 10 + x - '0');
- return in;
- }
- Input& operator >> (Input& in, int& u) {
- char x;
- while (~(x = in.pick()) && !digit(x) && x != '-');
- int aflag = ((x == '-') ? (x = in.pick(), -1) : (1));
- for (u = x - '0'; ~(x = in.pick()) && digit(x); u = u * 10 + x - '0');
- u *= aflag;
- return in;
- }
- Input& operator >> (Input& in, long long& u) {
- char x;
- while (~(x = in.pick()) && !digit(x) && x != '-');
- int aflag = ((x == '-') ? (x = in.pick(), -1) : (1));
- for (u = x - '0'; ~(x = in.pick()) && digit(x); u = u * 10 + x - '0');
- u *= aflag;
- return in;
- }
- Input in (stdin);
- const int N = 6e5 + 5;
- const int N2 = N << 1;
- int T, n;
- Zi fac[N2], _fac[N2];
- void init_fac(int l, int r) {
- for (int i = l; i <= r; i++) {
- fac[i] = fac[i - 1] * i;
- }
- _fac[r] = ~fac[r];
- for (int i = r; i > l; i--) {
- _fac[i - 1] = _fac[i] * i;
- }
- }
- void init_fac(int n) {
- static int old = 0;
- fac[0] = 1, _fac[0] = 1;
- if (n > old) {
- init_fac(old + 1, n);
- old = n;
- }
- }
- Zi comb(int n, int m) {
- return (n < m) ? (0) : (fac[n] * _fac[m] * _fac[n - m]);
- }
- Zi C(int x, int y) {
- return comb(x + y, x);
- }
- Zi S(int x, int y) {
- if (y + 1 <= x)
- return 0;
- return (y == n) ? (1) : (C(n - x, n - y) - C(n + 1 - x, n - 1 - y));
- }
- boolean vis[N];
- int main() {
- freopen("inverse.in", "r", stdin);
- freopen("inverse.out", "w", stdout);
- in >> T;
- while (T--) {
- in >> n;
- if (!n) {
- puts("0");
- continue;
- }
- init_fac(n << 1);
- memset(vis, false, n + 2);
- int mx = 0, sc = 1, i = 1, a;
- Zi ans = 0;
- for (i = 1; i < n; i++) {
- in >> a;
- if (a > mx) {
- for (int j = mx; j < a; j++) {
- ans += S(i, j);
- }
- mx = a;
- } else {
- while (vis[sc]) sc++;
- if (sc ^ a) {
- ans += S(i, mx);
- break;
- }
- }
- vis[a] = true;
- }
- if (i == n) {
- in >> a;
- ans += 1;
- } else {
- while (++i <= n)
- in >> a;
- }
- ans = S(0, 0) - ans;
- printf("%d\n", ans.v);
- }
- return 0;
- }
loj 2719 「NOI2018」冒泡排序 - 组合数学的更多相关文章
- Loj #2719. 「NOI2018」冒泡排序
Loj #2719. 「NOI2018」冒泡排序 题目描述 最近,小 S 对冒泡排序产生了浓厚的兴趣.为了问题简单,小 S 只研究对 *\(1\) 到 \(n\) 的排列*的冒泡排序. 下面是对冒泡排 ...
- LOJ 2719 「NOI2018」冒泡排序——模型转化
题目:https://loj.ac/problem/2719 首先要发现合法的充要条件是 | LDS | <=2 ! 因为有没用的步数,说明一个元素先往左移.又往右移(不会先往右移再往左移,因为 ...
- LOJ #2719. 「NOI2018」冒泡排序(组合数 + 树状数组)
题意 给你一个长为 \(n\) 的排列 \(p\) ,问你有多少个等长的排列满足 字典序比 \(p\) 大 : 它进行冒泡排序所需要交换的次数可以取到下界,也就是令第 \(i\) 个数为 \(a_i\ ...
- LOJ #2721. 「NOI2018」屠龙勇士(set + exgcd)
题意 LOJ #2721. 「NOI2018」屠龙勇士 题解 首先假设每条龙都可以打死,每次拿到的剑攻击力为 \(ATK\) . 这个需要支持每次插入一个数,查找比一个 \(\le\) 数最大的数(或 ...
- loj#2718. 「NOI2018」归程
题目链接 loj#2718. 「NOI2018」归程 题解 按照高度做克鲁斯卡尔重构树 那么对于询问倍增找到当前点能到达的高度最小可行点,该点的子树就是能到达的联通快,维护子树中到1节点的最短距离 s ...
- loj#2721. 「NOI2018」屠龙勇士
题目链接 loj#2721. 「NOI2018」屠龙勇士 题解 首先可以列出线性方程组 方程组转化为在模p意义下的同余方程 因为不保证pp 互素,考虑扩展中国剩余定理合并 方程组是带系数的,我们要做的 ...
- LOJ2719 「NOI2018」冒泡排序
「NOI2018」冒泡排序 题目描述 最近,小S 对冒泡排序产生了浓厚的兴趣.为了问题简单,小 S 只研究对 1 到n 的排列的冒泡排序. 下面是对冒泡排序的算法描述. 输入:一个长度为n 的排列p[ ...
- 「NOI2018」冒泡排序
「NOI2018」冒泡排序 考虑冒泡排序中一个位置上的数向左移动的步数 \(Lstep\) 为左边比它大的数的个数,向右移动的步数 \(Rstep\) 为右边比它大的数的个数,如果 \(Lstep,R ...
- loj#2720. 「NOI2018」你的名字
链接大合集: loj uoj luogu bzoj 单纯地纪念一下写的第一份5K代码.../躺尸 因为ZJOI都不会所以只好写NOI的题了... 总之字符串题肯定一上来就拼个大字符串跑后缀数组啦! ( ...
随机推荐
- 电商项目搜寻功能(分页,高亮,solr,规格过滤,价格的排序)
package cn.wangju.core.service; import cn.wangju.core.pojo.item.Item; import cn.wangju.core.util.Con ...
- 这个meta标签会让华为mate10 pro自带浏览器无法粘贴手机收到的验证码信息
前言 最近在项目中遇到一个问题,注册登录界面点击获取验证码,手机收到短信验证码后可以复制成功,但无法粘贴 让人郁闷的是在其它上手机上的(比如小米,苹果)默认浏览器和其它手机浏览器(比如QQ,夸克,搜 ...
- rpc和webservice的关系简述
RPC(Remote Procedure Call,远程过程调用)是一个很大的概念.它是一种通过网络从远程计算机程序上跨语言跨平台的请求服务.RPC能省略部分接口代码的开发,可以跨机器之间访问对象(J ...
- 一些 Java 和 Android 的参考资料
1. .net程序员转战android第三篇---登录模块之静态登录 2. .net程序员转战android第二篇---牛刀小试 3. .net程序员转战android第一篇---环境部署 4. 一些 ...
- Kubernetes Secret(机密存储)
Kubernetes Secret(机密存储) 官方文档:https://kubernetes.io/docs/concepts/configuration/secret/ 加密数据并存放Etcd中, ...
- CentOS 6.9安装MySQL 5.6 (使用yum安装)
CentOS 6.9安装MySQL 5.6 (使用yum安装) 移除CentOS默认的mysql-libs [root@test01 srv]# whereis mysqlmysql: /usr/li ...
- git和小乌龟在windows下安装
一:所需软件 (1):git 下载地址:https://git-scm.com/download (2):TortoiseGit 下载地址:https://tortoisegit.org/downlo ...
- 《深入理解Java虚拟机》并发(第12~13章)笔记
volatile关键字的作用 所有变量的可见性--仅仅是修改后的值的可见性,不保证并发修改时新值和预期一致.即只保证读,不保证写. 禁止指令重排序--修饰的变量,读写不会指令重排.如变量isReady ...
- 基于vue+springboot+docker网站搭建【九】负载均衡
后台mall-admin 负载均衡 1.新启动一个mall-admin docker实例 docker run -p 9002:9001 --name mall-admin-9002 --link m ...
- Vue实战狗尾草博客管理系统第二章
伙伴们出来啦,探讨各问题,关于项目中大量的表单,大家是怎么处理的? 本章主要内容如下:底层布局,路由配置,github仓库推送关联. 关联GitHub仓库 关联建立在github已创建账号的基础上 登 ...