zoukankan      html  css  js  c++  java
  • 【NOI 2018】冒泡排序(组合数学)

    题意大概是给定一个长度为$n$的排列$p$,求有多少长度为$n$的排列满足冒泡排序的交换次数为$frac{1}{2} sumlimits_{i = 1}^{n}|i - p_{i}|$。

    可以发现,该式子是冒泡排序复杂度的下界,任意一个数想要回到规定的位置至少要被交换$|i - p_{i}|$次,即在排序过程中不浪费任何一次交换,每一个数都只能向它归回原位的方向走。

    稍加思索,可以得出一个结论:

    1. 任何一个最长下降子序列长度超过$2$的排列一定是不合法的。
    2. 任何一个最长下降子序列不超过$2$的排列一定合法。

    考虑第一句话的正确性:

    试想处于中间的那个数,左边存在比它大的数,右边存在比它小的数,那么该数一定会被向左交换一次,也一定会被向右交换一次,然后这两次交换是对该数无意义的,因为它向两个方向走了,导致位置没有改变。所以这两次的交换被浪费,故一定不合法。

    考虑第二句话的正确性:

    试想一个逆序对,处于右边小的数一定会向左走,如果要使它不合法,必定要令它向右至少走一步,那就意味着右边存在更小的数,与原条件矛盾,对于左边的数同理。

     有了这个结论,不考虑起始字典序的限制,我们就可以写出一个$O(n^{2})$的dp,考虑$f_{i,j}$表示前$i$数,记$i$个数中最大的数为$mx$,剩余数中有$j$个数比$mx$小,此时的方案数。

    此处有两种转移:

    1. 选一个比$mx$大的数,不会破坏上述结论。
    2. 选剩余几个比$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)$的。 

    $igodot$技巧&套路:

    • 冒泡排序的复杂度(交换次数)分析。
    • 随机游走和括号序列计数的联系。
    • 有限制的括号序列计数的容斥转化技巧。
     1 #include <cstdio>
     2 #include <cstring>
     3 #include <algorithm>
     4 
     5 typedef long long LL;
     6 const int N = 1200005, MOD = 998244353;
     7 
     8 int tc, n;
     9 int fac[N], ifac[N], p[N >> 1], vis[N >> 1];
    10 
    11 inline void Read(int &x) {
    12   x = 0; static char c;
    13   for (c = getchar(); c < '0' || c > '9'; c = getchar());
    14   for (; c >= '0' && c <= '9'; x = (x << 3) + (x << 1) + c - '0', c = getchar());
    15 }
    16 
    17 inline int Pow(int x, int b) {
    18   static int re;
    19   for (re = 1; b; b >>= 1, x = (LL) x * x % MOD)
    20     if (b & 1) re = (LL) re * x % MOD;
    21   return re;
    22 }
    23 
    24 inline int C(int x, int y) {
    25   if (x < y) return 0;
    26   return (LL) fac[x] * ifac[y] % MOD * ifac[x - y] % MOD;
    27 }
    28 inline int Cal(int x, int y) {
    29   if (x < y) return 0;
    30   int dis = 2 * x - y, re1 = C(dis, x);
    31   if (y + 2 > dis) return re1;
    32   return (re1 - C(dis, (dis + y) / 2 + 1)) % MOD;
    33 }
    34 
    35 int main() {
    36   freopen("inverse.in", "r", stdin);
    37   freopen("inverse.out", "w", stdout);
    38   
    39   fac[0] = 1;
    40   for (int i = 1; i < N; ++i) {
    41     fac[i] = (LL) fac[i - 1] * i % MOD;
    42   }
    43   ifac[N - 1] = Pow(fac[N - 1], MOD - 2);
    44   for (int i = N - 1; i >= 1; --i) {
    45     ifac[i - 1] = (LL) ifac[i] * i % MOD;
    46   }
    47   
    48   scanf("%d", &tc);
    49   for (; tc; --tc) {
    50     int re = 0, mx = 0, low = 1, he = 0;
    51     memset(vis, 0, sizeof vis);
    52     scanf("%d", &n);
    53     for (int i = 1; i <= n; ++i) {
    54       Read(p[i]);
    55     }
    56     for (int i = 1; i < n; ++i, --he) {
    57       if (mx < p[i]) he += p[i] - mx;
    58       mx = std::max(mx, p[i]);
    59       for (vis[p[i]] = 1; vis[low]; ++low);
    60       re = (re + Cal(n - i + 1, he + 1)) % MOD;
    61       if (mx > p[i] && p[i] > low) {
    62         break;
    63       }
    64     }
    65     printf("%d
    ", (re + MOD) % MOD);
    66   }
    67 
    68   return 0;
    69 }
    View Code
  • 相关阅读:
    java 多线程
    数据结构与算法-----快速排序
    JS 强制类型转化
    VS Code 编辑器
    MySQL8数据库安装配置和启动
    listview更改选中时item背景色(转)
    Android下用程序的方法为ListView设置分割线Divider样式
    listview android:cacheColorHint,android:listSelector属性作用
    Android手动显示和隐藏软键盘
    android调试debug快捷键
  • 原文地址:https://www.cnblogs.com/Dance-Of-Faith/p/9339572.html
Copyright © 2011-2022 走看看