zoukankan      html  css  js  c++  java
  • @loj


    @description@

    最近,小 S 对冒泡排序产生了浓厚的兴趣。为了问题简单,小 S 只研究对 1 到 n 的排列的冒泡排序。

    下面是对冒泡排序的算法描述。

    输入:一个长度为 n 的排列 p[1...n]
    输出:p 排序后的结果。
    for i = 1 to n do
    	for j = 1 to n - 1 do
    		if(p[j] > p[j + 1])
    			交换 p[j] 与 p[j + 1] 的值
    

    冒泡排序的交换次数被定义为交换过程的执行次数。可以证明交换次数的一个下界是 (frac{1}{2}sum_{i=1}^{n}|i-p_i|),其中 (p_i) 是排列 p 中第 i 个位置的数字。如果你对证明感兴趣,可以看提示。

    小 S 开始专注于研究长度为 n 的排列中,满足交换次数 (frac{1}{2}sum_{i=1}^{n}|i-p_i|) 的排列(在后文中,为了方便,我们把所有这样的排列叫「好」的排列)。他进一步想,这样的排列到底多不多?它们分布的密不密集?

    小 S 想要对于一个给定的长度为 n 的排列 q,计算字典序严格大于 q 的“好”的排列个数。但是他不会做,于是求助于你,希望你帮他解决这个问题,考虑到答案可能会很大,因此只需输出答案对 (998244353) 取模的结果。

    原题传送门。

    @solution@

    达到下界的要求:一个数只能往前/往后移动。即不存在 (x < y < z) 满足 (a_x > a_y > a_z),等价于最长下降子序列长度 ≤ 2。

    然后打表发现它是个卡特兰数,冷静分析发现这道题就是个折线路径问题,写个组合数就过了。

    可以写 dp(i, j) 表示从前往后放数,前 i 个数的最大值为 j。

    分析 dp 转移式的组合意义,其对应不跨越直线 y = x,从某个格点走到 (n, n) 的方案数。

    这里给出一类不经由动态规划(虽然本质一样)的分析过程。

    考虑构造排列的前缀最大值序列 (m_i = max_{j=1}^{i}{a_j})

    不难发现一个序列 ({m_i}) 是“某个排列前缀最大值序列”的等价条件为(1)(m_{i-1} leq m_i)(2)(ileq m_ileq n)。对应从 (1, 1) 走到 (n, n),必须经过 y = x 的右上方的方案数。即卡特兰数。

    我们可以证明“前缀最大值序列”与“最长下降子序列长度 ≤ 2 的序列”一一对应:

    (1)“最长下降子序列长度 ≤ 2 的序列” -> “前缀最大值序列”:显然。

    (2)“前缀最大值序列” -> “最长下降子序列长度 ≤ 2 的序列”:如果 (m_i ot= m_{i-1}),则构造 (a_i = m_i);否则构造 (a_i) 为当前未使用的最小数。

    于是我们可以转化成求前缀最大值序列。之后做类数位 dp 的过程即可。
    根据上面的构造过程,注意及时排除掉 (m_i = m_{i-1})(a_i) 不是当前未使用的最小数情况。

    @accepted code@

    #include <cstdio>
    #include <algorithm>
    using namespace std;
    
    const int MAXN = 2*600000;
    const int MOD = 998244353;
    
    inline int add(int x, int y) {x += y; return x >= MOD ? x - MOD : x;}
    inline int sub(int x, int y) {x -= y; return x < 0 ? x + MOD : x;}
    inline int mul(int x, int y) {return (int)(1LL * x * y % MOD);}
    
    int pow_mod(int b, int p) {
    	int ret = 1;
    	for(int i=p;i;i>>=1,b=mul(b,b))
    		if( i & 1 ) ret = mul(ret, b);
    	return ret;
    }
    
    int read() {
    	int x = 0, ch = getchar();
    	while( ch > '9' || ch < '0' ) ch = getchar();
    	while( '0' <= ch && ch <= '9' ) x = 10*x + ch - '0', ch = getchar();
    	return x;
    }
    
    int fct[MAXN + 5], ifct[MAXN + 5];
    void init() {
    	fct[0] = 1; for(int i=1;i<=MAXN;i++) fct[i] = mul(fct[i - 1], i);
    	ifct[MAXN] = pow_mod(fct[MAXN], MOD - 2);
    	for(int i=MAXN-1;i>=0;i--) ifct[i] = mul(ifct[i + 1], i + 1);
    }
    int comb(int n, int m) {
    	if( n < m || m < 0 ) return 0;
    	else return mul(fct[n], mul(ifct[m], ifct[n-m]));
    }
    int func(int x, int y) {
    	if( y < 0 ) return 0;
    	else return sub(comb(x + y, x), comb(x + y, x + 1));
    }
    
    int a[MAXN + 5]; bool tag[MAXN + 5];
    void solve() {
    	int n = read();
    	for(int i=1;i<=n;i++)
    		a[i] = read(), tag[i] = false;
    	
    	int ans = 0, mx = 0, nowmin = 1;
    	for(int i=1;i<=n;i++) {
    		if( a[i] < mx ) {
    			ans = add(ans, func(n - i + 1, n - mx - 1));
    			if( a[i] != nowmin ) break;
    		}
    		else mx = a[i], ans = add(ans, func(n - i + 1, n - mx - 1));
    		
    		tag[a[i]] = true; while( tag[nowmin] ) nowmin++;
    	}
    	printf("%d
    ", ans);
    }
    
    int main() {
    	freopen("inverse.in", "r", stdin);
    	freopen("inverse.out", "w", stdout);
    	
    	init(); for(int T=read();T;T--) solve();
    }
    

    @details@

    对于组合数 ({nchoose m}),如果 (n < m)(m < 0) 需要特判返回 0。

  • 相关阅读:
    DNS 域名系统服务
    tomcat
    mysql+redis
    centos7搭建lnmp
    redis安装
    redis 高级应用
    ubuntu,安装、配置和美化(1)
    解决大于5.7版本mysql的分组报错Expression #1 of SELECT list is not in GROUP BY clause and contains nonaggregated column 'userinfo.
    交换机常用命令
    SSH爆破应急响应
  • 原文地址:https://www.cnblogs.com/Tiw-Air-OAO/p/13083112.html
Copyright © 2011-2022 走看看