zoukankan      html  css  js  c++  java
  • [洛谷P4769] NOI2018 冒泡排序

    问题描述

    最近,小 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 取模的结果。

    输入格式

    输入第一行包含一个正整数 (T),表示数据组数。

    对于每组数据,第一行有一个正整数 (n),保证 (n leq 6 imes 10^5)

    接下来一行会输入 (n) 个正整数,对应于题目描述中的 (q_i),保证输入的是一个 (1)(n) 的排列。

    输出格式

    输出共 (T) 行,每行一个整数。

    对于每组数据,输出一个整数,表示字典序严格大于 (q) 的「好」的排列个数对 998244353 取模的结果。

    样例输入

    1
    3
    1 3 2

    样例输出

    3

    解析

    我们先考虑什么样的排列满足交换次数等于下界。由下界的证明过程,满足要求的排列在交换时是不会走重复的路程的(具体可以体会一下1 4 3 2,3需要先和左边比他大的元素交换,再和右边比他小的元素交换)。所以,如果排列中不存在长度为3及以上的下降子序列时,该排列满足要求。

    假设没有字典序的要求,我们考虑计算满足要求的排列个数。设 (f_{i,j}) 表示长度为 (i) 、第一个元素为 (j) 的满足条件的排列个数。转移分三种情况:

    • 第二个元素 (k)(j) 大。如果 (k) 不能和后面组成长度为 3 以上的下降序列,那么 (j) 肯定也不能。因此,我们把从第二个开始的每一个比 (j) 大的元素都减一,把后面当做一个排列,满足要求的方案数就是 (f_{i-1,k})
    • 第二个元素 (k)(j) 小且不等于 1 。那么排列中一定存在 (k,j,1) 的下降子序列。方案数为0。
    • 第二个元素等于 1 。那么 ([1,j-1]) 的元素必须递增排列(位置上不一定是连续的)。但方案数显然不是 (f_{i-1,1}) 。我们需要转化一下,不妨将 1 移动到 2 , 2 移动到 3 ,……,j-1 移动到 1,那么序列就变成了 j-1 开头。可以证明,(f_{i-1,j})等价于我们要求的方案数。

    综上所述,我们有如下转移方程:

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

    接下来考虑字典序的限制。我们可以枚举公共前缀的长度来解决这个问题,那么我们可以把后面离散化后当做一个长度为 (n-i+1) 的排列,而第一位要大于原排列的对应位置。设当前在第 (i) 位,([i,n]) 中有 (j) 个数比 ([1,i]) 中的最大值小(或等于)。第 (i) 位上不能放后缀最小值,这会使字典序达不到要求;同样,也不能放比前缀最大值小的值,这样一定会有长度为3的下降子序列。如果原排列中 (i) 上就是前缀最大值,构造的排列中第 (i) 位上由于字典序的限制也不能放前缀最大值。所以,我们有:

    [Ans=sum_{i=1}^{n-1}sum_{k=j+1}^{n-i+1}f_{n-i+1,k} ]

    这显然就是一个关于 (f_i) 的后缀和。我们接下来考虑如何 (O(1)) 求出 (f_I) 的后缀和。记后缀和为 (S_{i,j}),由转移方程,我们不难发现:

    [S_{i,j}=S_{i-1,j-1}+S_{i,j+1} ]

    考虑组合意义,求 (S_{n,m}) 相当于从左上角 ((0,0)) 出发,每次能够往右下或左走,求到达右下角 $(n,m) $ 的方案数。先考虑到 ((n,0)),每次向右下走之后都必须有一个向左走的操作与之对应,才能够回到第 (0) 列。这相当于括号匹配,答案就是卡特兰数。

    [S_{n,0}=C_n=C_{2u}^n-C_{2n}^{n-1} ]

    推广到所有,我们有:

    [S_{n,m}=C_{2n-m}^{n-m}-C_{2n-m}^{n-m-1} ]

    证明的话可以利用不能走到 (y=-1) 这条直线上来的性质。注意枚举前缀时还要判断前缀是否合法,记录一下前缀最大值之后的次大值即可。而计算比前缀最大值小的个数可以用树状数组来解决。

    题解比代码长

    代码

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #define int long long
    #define N 600002
    #define M 1200000
    using namespace std;
    const int mod=998244353;
    int t,n,i,p[N],maxx[N],minx[N],cnt[N],c[N],fac[2*N],inv[2*N];
    int read()
    {
    	char c=getchar();
    	int w=0;
    	while(c<'0'||c>'9') c=getchar();
    	while(c<='9'&&c>='0'){
    		w=w*10+c-'0';
    		c=getchar();
    	}
    	return w;
    }
    int poww(int a,int b)
    {
    	int ans=1,base=a;
    	while(b){
    		if(b&1) ans=ans*base%mod;
    		base=base*base%mod;
    		b>>=1;
    	}
    	return ans;
    }
    int lowbit(int x)
    {
    	return x&(-x);
    }
    void add(int x,int y)
    {
    	for(int i=x;i<=n;i+=lowbit(i)) c[i]+=y;
    }
    int ask(int x)
    {
    	int ans=0;
    	for(int i=x;i>=1;i-=lowbit(i)) ans+=c[i];
    	return ans;
    }
    int C(int n,int m)
    {
    	return fac[n]*inv[m]%mod*inv[n-m]%mod;
    }
    int S(int n,int m)
    {
    	return (C(2*n-m,n-m)-C(2*n-m,n-m-1)+mod)%mod;
    }
    signed main()
    {
    	t=read();
    	for(i=fac[0]=1;i<=M;i++) fac[i]=fac[i-1]*i%mod;
    	inv[M]=poww(fac[M],mod-2);
    	for(i=M-1;i>=0;i--) inv[i]=inv[i+1]*(i+1)%mod;
    	while(t--){
    		n=read();
    		memset(c,0,sizeof(c));
    		for(i=1;i<=n;i++) p[i]=read();
    		for(i=1;i<=n;i++) maxx[i]=max(maxx[i-1],p[i]);
    		for(i=n,minx[n+1]=1<<30;i>=1;i--) minx[i]=min(minx[i+1],p[i]);
    		for(i=n;i>=1;i--){
    			add(p[i],1);
    			cnt[i]=ask(maxx[i]);
    		}
    		int ans=0,max1=0;
    		for(i=1;i<=n;i++){
    			if(max1>minx[i]) break;
    			if(cnt[i]+1<=n-i+1) ans=(ans+S(n-i+1,cnt[i]+1))%mod;
    			if(p[i]<maxx[i]) max1=max(max1,p[i]);
    		}
    		printf("%lld
    ",ans);
    	}
    	return 0;
    }
    
  • 相关阅读:
    python读写excel利器:xlwings 从入门到精通
    认识--类 ( Class ) 面向对象技术
    python 平均值/MAX/MIN值 计算从入门到精通
    python读写word文档 -- python-docx从入门到精通
    【模板】KMP算法
    【模板】主席树
    C语言第一次博客作业
    C语言--第0次作业
    Chapter5:语句
    Chapter4:表达式
  • 原文地址:https://www.cnblogs.com/LSlzf/p/13457507.html
Copyright © 2011-2022 走看看