[提高组集训2021] 就差⼀点

一、题目

对一个长度为 (n) 的排列冒泡排序,给定参数 (k),问有多少排列排序后存在一个大小为 (n-1) 的上升子序列。

for(int i=1;i<=k;i++)
	for(int j=1;j<n;j++)
		if(p[j]>p[j+1]) swap(p[j],p[j+1])

(nleq 5000)

二、解法

首先介绍一下反序表,解决排列问题的又一利器 ( t get)

定义:定义数组 (d_i=sum_{j=1}^{i}[p_j<p_i]) 表示排列 (p) 的反序表,当然也可以定义成 (d_{p_i}=sum_{j=1}^i[p_j<p_i])

性质1:反序表和排列构造双射,因为反序表可以通过插入法构造出对应的排列。

性质2:一个排列被冒泡排序的次数是 (max d_i)每次冒泡把 (d_i) 中的非 (0) 元素减 (1) 然后前移一位。


本题套用上面的结论即可,首先我们考虑能被排好序的排列个数怎么求,其实就是反序表前 (k) 个元素任意取值,后 (n-k) 个元素只能取 ([0,k]),方案是 (k! imes(k+1)^{n-k}),我们把没被排好序的方案分成两种情况:

情况1:排序后反序表为:(00..011110..00),也就是中间出现了连续的一段 (1),那么这段 (1) 初始的元素值一定是 (k+1),我们枚举这一段 (1) 的长度,可以得到初始反序表的方案数是:

[k!sum_{i=1}^{n-k-1}(k+1)^{n-k-i} imes(n-k-i) ]

情况2:排序后反序表为:(00..00x00..00),其中 (x>1),我们枚举它初始的位置是 (i+k),那么初始反序表的方案数是:

[k!sum_{i=3}^{n-k}(i-2) imes(k+1)^{n-k-1} ]

把所有情况的方案加起来即可。

#include <cstdio>
#include <iostream>
using namespace std;
#define int long long
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int T,n,m,k,pw[5005];
signed main()
{
	//freopen("almostthere.in","r",stdin);
	//freopen("almostthere.out","w",stdout);
	T=read();
	while(T--)
	{
		n=read();k=read();m=read();
		pw[0]=1;k=min(k,n);
		for(int i=1;i<=n;i++)
			pw[i]=pw[i-1]*(k+1)%m;
		int ans=pw[n-k];
		for(int i=1;i<n-k;i++)
			ans=(ans+(n-k-i)*pw[n-k-i])%m;
		for(int i=3;i<=n-k;i++)
			ans=(ans+(i-2)*pw[n-k-1])%m;
		for(int i=1;i<=k;i++)
			ans=ans*i%m;
		printf("%lld
",ans);
	}
}
原文地址:https://www.cnblogs.com/C202044zxy/p/15249814.html