题目:

洛谷 4769

博客页面左下角的嘴嘴瓜封神之战中的题目

分析:

一个排列交换次数为 \(\frac{1}{2}\sum_{i=1}^{n}|i-p_i|\) 的充要条件是这个排列不存在长度为 \(3\) 的下降序列(即:最长下降子序列不超过 \(2\) ),证明 感性理解如下:

考虑如果交换次数大于 \(\frac{1}{2}\sum_{i=1}^{n}|i-p_i|\) ,那么一定存在至少一个元素「绕路」了。

必要性 :「绕路」分为如下两种情况:

第一,某个元素的目标位置在它左侧,但它往右移动了。如果出现这种情况,那么它右边一定紧贴着一个比它小的元素。既然右边必然有比它小的元素,那么左边的所有元素必须都比它小才能满足不存在长度为 \(3\) 的下降序列。然而,由于它的目标位置在当前位置的左边,所以比它小的元素数量比当前在它左边的元素数量少。所以,如果这个元素绕路,那么一定存在长度为 \(3\) 的下降序列。

第二,某个元素的目标位置在它右侧,但它往左移动了。如果出现这种情况,那么导致它向左移动的一定是一个它左侧的比它大的元素。同时,和上面类似,右侧不可能所有元素都比它大。所以也一定存在长度为 \(3\) 的下降序列。

充分性 :若存在 \(i<j<k\) 满足 \(p_i>p_j>p_k\) (即一个长度为 \(3\) 的下降序列),由于最终状态是有序的,那么 \(p_j\) 必然和 \(p_i\) 和 \(p_k\) 分别交换过一次。和 \(p_i\) 交换时 \(p_j\) 向左运动,和 \(p_k\) 交换时 \(p_j\) 向右运动,而 \(p_j\) 的目标要么在原来左边,要么在原来右边,所以一定「绕路」了。

先忽略字典序的条件,那么问题就转换成了不存在长度为 \(3\) 的下降序列的排列数。 打表发现是卡特兰数。

考虑 动态规划 递推。如果现在已经填好了前 \(i\) 个位置,下一个位置怎么填呢?为了保证不出现长度为 \(3\) 的下降序列,只有两种填法:填任意一个比前 \(i\) 个数都大的数,或填最小的还没有出现过的数。如果按照这两种策略填,显然不会出现长度为 \(3\) 的下降序列(充分性);如果不按照这两种策略,即填了一个比当前最大值小,但不是最小的数,那么当前最大值、现在填的数和没出现过的最小值(因为是个排列,每个数必须都出现,所以现在不填以后就得填)会构成一个长度为 \(3\) 的下降序列(必要性)。

那么设 \(f_{i,j}\) 表示填好了 \(i\) 个数,最大值为 \(j\) 时接下来填法的方案数,有如下转移(最大值不变时对应着「填没出现过的最小值」这唯一一种方案):

\[f_{i,j}=\sum_{k=1}^{j-1}f_{i+1,k}+f_{i+1,j}=\sum_{k=1}^jf_{i-1,k}
\]

直接算是 \(O(n^3)\) 的,前缀和优化一下也是 \(O(n^2)\) ,显然过不去。但是你打表都打出卡特兰数了,还不往组合数那边想想是为啥吗?

卡特兰数那一套推导过程经常用在二维平面上走路来思考对吧(自信脸 .jpg )。考虑这道题,点 \((i,j)\) 能走到 \((i+1,k)\) 其中 \(j\leq k\leq n\) ,而 \(f_{i,j}\) 就是从 \((i,j)\) 走到 \((n,n)\) 的方案数。走的步长不一定怎么办呢?我们发现走的步数是确定的 \(n-i\) ,而总步长也是确定的 \(n-j\) ,相当于 \(n-i\) 个非负整数之和是 \(n-j\) 的方案数。那么用一下插板法,得到方案数就是 \(C_{n-i+n-j-1}^{n-i-1}\) 。注意,走的时候要时刻保证 \(i\leq j\) ,即要时刻保证在直线 \(y=x-1\) 上方(不能碰到)。按照卡特兰数的套路,对于每种不合法的方案都可以将其第一次碰到上述直线之前的部分关于该直线对称,这样就和从 \((i,j)\) 关于 \(y=x-1\) 的对称点 \((j+1,i-1)\) 。同样可以用插板法计算它到 \((n,n)\) 的方案数,即不合法方案数。

综上所述:

\[f_{i,j}=C_{2n-i-j-1}^{n-i-1}-C_{2n-i-j-1}^{n-j-2}
\]

于是可以 \(O(1)\) 算出任意一个 \(f\) 值了。

噢,别忘了还有个字典序的限制。大力枚举前 \(i\) 个字符相同,并维护此时的最大值 \(j\) 。那么此时对答案的贡献就是 \(\sum_{k=j+1}^{n}f_{i+1,k}=f_{i,j+1}\) (只能往大填。如果填没出现过的最小值肯定不满足字典序的限制) 。注意如果枚举过程中已经出现了长度为 \(3\) 的下降序列,那么后面一定没有合法方案,直接退出。

代码:

#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cctype>
using namespace std; namespace zyt
{
template<typename T>
inline bool read(T &x)
{
char c;
bool f = false;
x = 0;
do
c = getchar();
while (c != EOF && c != '-' && !isdigit(c));
if (c == EOF)
return false;
if (c == '-')
f = true, c = getchar();
do
x = x * 10 + c - '0', c = getchar();
while (isdigit(c));
if (f)
x = -x;
return true;
}
template<typename T>
inline void write(T x)
{
static char buf[20];
char *pos = buf;
if (x < 0)
putchar('-'), x = -x;
do
*pos++ = x % 10 + '0';
while (x /= 10);
while (pos > buf)
putchar(*--pos);
}
typedef long long ll;
const int N = 1.2e6 + 10, p = 998244353;
int n, fac[N], finv[N];
inline int power(int a, int b)
{
int ans = 1;
while (b)
{
if (b & 1)
ans = (ll)ans * a % p;
a = (ll)a * a % p;
b >>= 1;
}
return ans;
}
inline int inv(const int a)
{
return power(a, p - 2);
}
void init()
{
fac[0] = 1;
for (int i = 1; i < N; i++)
fac[i] = (ll)fac[i - 1] * i % p;
finv[N - 1] = inv(fac[N - 1]);
for (int i = N - 1; i > 0; i--)
finv[i - 1] = (ll)finv[i] * i % p;
}
inline int C(const int n, const int m)
{
if (n < 0 || m < 0 || n < m)
return 0;
else
return (ll)fac[n] * finv[m] % p * finv[n - m] % p;
}
int cal(const int i, const int j)
{
if (i > j)
return 0;
else
return (C(n * 2 - i - j - 1, n - i - 1) - C(n * 2 - i - j - 1, n - (j + 1) - 1) + p) % p;
}
bool vis[N];
int work()
{
int T;
read(T);
init();
while (T--)
{
read(n);
memset(vis, 0, sizeof(bool[n + 1]));
int ans = 0, pos = 1, mx = 0;
bool flag = true;
for (int i = 1; i <= n; i++)
{
int a;
read(a);
mx = max(mx, a);
ans = (ans + cal(i - 1, mx + 1)) % p;
if (flag && a < mx && a != pos)
{
write(ans);
flag = false;
}
vis[a] = true;
while (vis[pos])
++pos;
}
if (flag)
write(ans);
putchar('\n');
}
return 0;
}
}
int main()
{
#ifdef BlueSpirit
freopen("4769.in", "r", stdin);
#endif
return zyt::work();
}

最新文章

  1. 【知识必备】RxJava+Retrofit二次封装最佳结合体验,打造懒人封装框架~
  2. java写入和写出EXCEL(含源代码)
  3. AngularJs定制样式插入到ueditor中的问题总结
  4. POJ2570 Fiber Network(Floyd)
  5. 将salt取到的数据处理
  6. db2 字符串转换 数字
  7. 2016年JavaScript技术栈展望
  8. SPOJ 4487 Splay 基本操作
  9. c#中字符串截取使用的方法
  10. sql server 查询出的结果集,拼接某一列赋值给一个变量
  11. Docker入门之四搭建私有仓库
  12. AlexNet 网络详解及Tensorflow实现源码
  13. C#线程等待句柄
  14. docker cs50 ide 安装
  15. DSAPI 短域名服务
  16. 利用 JMetal 实现大规模聚类问题的研究(一)JMetal配置
  17. Spring基于AspectJ的AOP的开发之AOP的相关术语
  18. Convert Application Model Differences
  19. 在spring中常被忽视的注解 @Primary
  20. 【移动端debug-2】Flexbox在移动端的兼容实践

热门文章

  1. Promise编程规范
  2. 选带傅里叶变换(zoom-fft)
  3. easyUI 验证控件应用、自己定义、扩展验证 手机号码或电话话码格式
  4. 深入浅出AOP(一)
  5. CUDA编程(十)使用Kahan&amp;#39;s Summation Formula提高精度
  6. jquery源码学习笔记三:jQuery工厂剖析
  7. Why was 80 Chosen as the Default HTTP Port and 443 as the Default HTTPS Port?
  8. ABAP 创建和调用WebService
  9. VA市场高烧已退,逐渐降温
  10. C # 踩坑记录(20190603)