题目传送门

  传送门

题目大意

  (相信大家都知道)

  显然要考虑一个排列$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」冒泡排序 - 组合数学的更多相关文章

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

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

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

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

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

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

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

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

  5. loj#2718. 「NOI2018」归程

    题目链接 loj#2718. 「NOI2018」归程 题解 按照高度做克鲁斯卡尔重构树 那么对于询问倍增找到当前点能到达的高度最小可行点,该点的子树就是能到达的联通快,维护子树中到1节点的最短距离 s ...

  6. loj#2721. 「NOI2018」屠龙勇士

    题目链接 loj#2721. 「NOI2018」屠龙勇士 题解 首先可以列出线性方程组 方程组转化为在模p意义下的同余方程 因为不保证pp 互素,考虑扩展中国剩余定理合并 方程组是带系数的,我们要做的 ...

  7. LOJ2719 「NOI2018」冒泡排序

    「NOI2018」冒泡排序 题目描述 最近,小S 对冒泡排序产生了浓厚的兴趣.为了问题简单,小 S 只研究对 1 到n 的排列的冒泡排序. 下面是对冒泡排序的算法描述. 输入:一个长度为n 的排列p[ ...

  8. 「NOI2018」冒泡排序

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

  9. loj#2720. 「NOI2018」你的名字

    链接大合集: loj uoj luogu bzoj 单纯地纪念一下写的第一份5K代码.../躺尸 因为ZJOI都不会所以只好写NOI的题了... 总之字符串题肯定一上来就拼个大字符串跑后缀数组啦! ( ...

随机推荐

  1. iframe嵌套页面中的跳转

    简单说一下场景. 假设有A.B.C和D四个JSP页面,D通过iframe嵌套在C中,C通过iframe嵌套在B中,B通过iframe嵌套在A中. 然后现在在D中编写JavaScript代码跳转页面. ...

  2. 在.net中读写config文件的各种方法【转】

    今天谈谈在.net中读写config文件的各种方法. 在这篇博客中,我将介绍各种配置文件的读写操作. 由于内容较为直观,因此没有过多的空道理,只有实实在在的演示代码, 目的只为了再现实战开发中的各种场 ...

  3. logical函数

    logical函数(逻辑函数) logical(x):x ~=0时,logical(x)=1:x = 0时,logical(x)=0

  4. 2019-11-27-WPF-全屏透明窗口

    原文:2019-11-27-WPF-全屏透明窗口 title author date CreateTime categories WPF 全屏透明窗口 lindexi 2019-11-27 09:22 ...

  5. Enum.GetValues(),返回System.Array的一个实例

    Array enumData = Enum.GetValues(e.GetType()); Console.WriteLine("This enum has {0} members.&quo ...

  6. PIE属性表多字段的文本绘制

    最近研究了PIE SDK文本元素的绘制相关内容,因为在我们的开发中,希望可以做到在打开一个Shp文件后,读取到属性表的所有字段,然后可以选择一些需要的字段,将这些字段的所有要素值的文本,绘制到shp图 ...

  7. 百度站长平台HTTPS认证所遇到的坑

    坑1: 百度站长平台https认证失败,提示:请确保您网站的所有链接均支持https访问,且未使用不安全协议(如:SSL2.SSL3等协议). 解决办法: 1.  友情链接检查, 要检查所有的友情链接 ...

  8. linux的ftp操作

    1.查看是否安装 rpm -qa |grep vsftpd 没有输出,表示没有安装 2.查看服务状态 1).service vsftpd status 输出信息 vsftpd is stopped 表 ...

  9. docker研究-4 docker镜像制作

    这次实验以centos镜像为基础镜像进行相关docker镜像制作. 1. 下载centos镜像 [root@localhost ~]# docker pull centosUsing default ...

  10. testlink 1.9.19安装

    环境平台: 系统:Centos 7.6 数据库:mysql 5.7 PHP版本:PHP 5.6 testlink版本:testlink- 链接:https://pan.baidu.com/s/10Pr ...