NOIP模拟 排序
涉及知识点:组合数学
题面
小Z是一位热爱优化算法的同学。
一天他在研究归并排序,并想到,如果在归并排序的过程中提前return,对正确率的影响并不会很大。
于是他写了如下部分代码:
void merge_arr(int l,int mid,int r)//此函数表示将S[1,mid],S[mid+1,r]两个有序序列合并成为一个大的有序序列S[l,r],如果原序列无序则合并后的序列也无序
{
//merge_array
}
void merge_sort(int l,int r,int deep)
{
if(l==r) return;
if(deep>=K) return;
int mid=(l+r)>>1;
merge_sort(l,mid,deep+1);
merge_sort(mid+1,r,deep+1);
merge_arr(l,mid,r);
}
其中,主函数内将使用如下方法调用排序:
merge_sort(1,n,0);
现在小Z要将一个长度为 的排列用此方式进行排序。显然这个方法不一定是正确的。现在他想知道,有多少给定的排列,在用此方法排序后,是有序的(单调递增则为有序)。
当然,为了找到一个最优的 ,小Z会进行多次询问,每次询问 相同, 不同。
为了减少中间运算与输出的麻烦,你只需要给出个数在 意义下的值即可。
分析
题目通过了一种类似线段树的方法将一个序列划分成了若干区间,我们可以发现,用题目给出方法排序的结果是有序的,当且仅当所有叶子结点所代表的区间( 或 )是有序的。很显然,当某个区间 的两个子区间 和 都是有序的,那么经过合并这个区间也是有序的;否则如果有子区间是无序的,那么合并后的大区间也是无序的。结合归并排序的知识点也可以得出该结论。
那么问题就转化为了:求多少种排列,使得这个排列划分成深度为 的线段树后,线段树的叶子结点的区间是有序的。
另外,很明显当 的时候,答案为 ,又因为 的范围为 ,所以我们需要处理的 的范围最大为 。
解法
80分做法
可以将问题反过来思考:给你一个长度为 的序列并将其划分为若干区间,再给你从 共 个数填入这个序列,使得这些区间中是有序的。很明显可以发现,假如一个长度为 的区间,选出来 个数,那么只有一种填入方式能使得他们有序,所以问题就转化为 个数选出 个数(顺序不影响),再选出 个数,……,最后选出 个数()有多少种方案
即
具体 的值就是每个“叶子结点区间”的长度,我们 模拟题目二分的操作即可
100分做法
我们换一种思路,首先令 ,也就是所有的排列数,对于每个“叶子结点区间”,要使得该区间内有序,那么这个区间内只有一种排列方法,相当于我们确定了这个区间内 个数的相对位置关系,因此 除以 ,这样遍历完所有深度为 的区间即可
即
注:只需要遍历深度严格为 的区间
为什么不需要遍历深度小于 的 的区间呢?因为这样的区间 ,带入上式可以发现不影响答案
并且,通过记录不同的 表示深度限制为 时的答案,我们可以一次处理得到所有答案
代码
#include<bits/stdc++.h>
using namespace std;
template<class T>inline void rd(T &x){
T res=0,f=1;
char ch=getchar();
while(!isdigit(ch)){if(ch=='-')f=-1; ch=getchar();}
while(isdigit(ch)){res=res*10+ch-'0';ch=getchar();}
x=res*f;
}
template<class T>inline void wt(T x,char endch='\0'){
static char wtbuff[20];
static int wtptr;
if(x==0){
putchar('0');
}
else{
if(x<0){x=-x;putchar('-');}
wtptr=0;
while(x){wtbuff[wtptr++]=x%10+'0';x/=10;}
while(wtptr--) putchar(wtbuff[wtptr]);
}
if(endch!='\0') putchar(endch);
}
typedef long long LL;
const LL P=998244353;
const int MAXN=5e6+5;
int t,n,k,n2[25],ans[25];
LL f[MAXN];
inline void preproc(){
n2[0]=1;
for(int i=1;i<=23;i++){
n2[i]=n2[i-1]*2;
}
f[0]=1;
for(LL i=1;i<=n;i++){
f[i]=f[i-1]*i%P;
}
}
void exgcd(LL a,LL b,LL& x,LL& y){
if(b==0){
x=1;y=0;
return;
}
exgcd(b,a%b,y,x);
y-=a/b*x;
}
inline LL inv(LL x){
LL xx,yy;
exgcd(x,P,xx,yy);
return (xx%P+P)%P;
}
inline LL com(LL up,LL down){
if(up>down) return 0;
if(up==down) return 1;
return f[down]*inv(f[up])%P*inv(f[down-up])%P;
}
void merge_sort(int l,int r,int deep)
{
if(l>r) return;
if(l==r) return;
ans[deep]=ans[deep]*inv(f[r-l+1])%P;
int mid=(l+r)>>1;
merge_sort(l,mid,deep+1);
merge_sort(mid+1,r,deep+1);
}
int main(){
freopen("sort.in","r",stdin);
freopen("sort.out","w",stdout);
rd(t);rd(n);
preproc();
fill(ans+1,ans+1+23,f[n]);
merge_sort(1,n,0);
ans[0]=1;
while(t--){
rd(k);
else if(k>=23 || n2[k]>=n) wt(f[n],'\n');
else wt(ans[k],'\n');
}
return 0;
}
附注
- 由于题目的计算都是在模意义下进行,需要用到乘法逆元
本文作者:MessageBoxA
本文链接:https://www.cnblogs.com/SkyNet-PKN/p/17715443.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步