• @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。

  • 相关阅读:
    Different AG groups have the exactly same group_id value if the group names are same and the ‘CLUSTER_TYPE = EXTERNAL/NONE’
    An example of polybase for Oracle
    use azure data studio to create external table for oracle
    Missing MSI and MSP files
    You may fail to backup log or restore log after TDE certification/key rotation.
    Password is required when adding a database to AG group if the database has a master key
    Use KTPASS instead of adden to configure mssql.keytab
    ardunio+舵机
    android webview 全屏100%显示图片
    glide 长方形图片显示圆角问题
  • 原文地址:https://www.cnblogs.com/Tiw-Air-OAO/p/13083112.html
Copyright © 2020-2023  润新知