给一个只排前k项的插入排序算法,问一个以1-n为元素的数组有多少种排列方式能在这个前k项排序算法完成之后能使最长上升子序列的长度达到n-1。
组合数学推公式的题目。首先我们发现前k个数字是会被排序的,所以前k个数字没有顺序要求,只要是1-k的话,随便怎么排都行,一个全排列就是阶乘k。前面都是1-k的情况下,后面n-k个数字可以把其中一个数字任意插入其他n-k-1个数字之间,这个公式胖清推出来了:
( n − k ) 2 + 2 × ( n − k − 1 ) (n-k)^2+2\times(n-k-1) (n−k)2+2×(n−k−1)
除此之外我们也能从前k个数里面取一个数去和后面的数字交换。任意从前k个数取一个数记为x,则x可以和k+1交换,这样只有x是无序的,可以插入后面任意的位置,后面其他数保持有序即可。这样的插入有:
k × ( n − k ) k\times(n-k) k×(n−k)
即有n-k种插入方式和k种交换方式(前k个数和k+1换),乘法原理。
k这个数比较特殊,它除了k+1可以换以外,还能和后面其他数字(我们记为y)交换,交换完以后只能呆在k+1的前方,这样无序的数就是y了,这种方式有:
n − k − 1 n-k-1 n−k−1
即有和n-k-1个数字交换的方式,但是只有一种插入方式(k+1前方)。
这三种摆放方式是并列的,所以加法原理加起来,然后乘上前面k个数字的全排列即可。
完整的公式为:
( ( n − k ) 2 + 2 × ( n − k − 1 ) + k × ( n − k ) + n − k − 1 ) × k ! ((n-k)^2+2\times(n-k-1)+k\times(n-k)+n-k-1)\times k! ((n−k)2+2×(n−k−1)+k×(n−k)+n−k−1)×k!
注意一下取模,公式方面就没有问题了。但是!但是!!!
k不一定小于n!!!k不一定小于n!!!k不一定小于n!!!
就是这个坑让我们自闭了两个小时,怎么推公式都是对的,自己写个函数检查都是对的,各种重算,结果看了题解就是这么个坑???一改就对了???
仿佛秦皇岛站没有看懂题意特判的事情又重现了。沈阳败了。
#include
using namespace std;
#define ll long long
const int maxn=50;
ll fac[maxn+5];
void init(ll mod){
fac[0]=1;
for(int i=1;i<=maxn;i++){
fac[i]=fac[i-1]*i%mod;//打阶乘表
}
}
ll get(ll k,ll mod){//前k个数字不换的情况,模块化编程,胖清提供
ll ans=k*k%mod;
ans=(ans-2*(k-1)+mod)%mod;
return ans;
}
void solve(int ca){
ll n,k,q;
scanf("%lld%lld%lld",&n,&k,&q);
init(q);
if (n<=k){//如果k大于等于n,等于说是全都排好序了,那你怎么排都行
printf("Case #%d: %lld\n",ca,fac[n]);
return;
}
ll t=get(n-k,q);
ll ans=(k*(n-k)%q+n-k-1+t)%q*fac[k]%q;//剩余的公式,阿忠哥和我推的
printf("Case #%d: %lld\n",ca,ans);
}
int main(){
//freopen("test.in","r",stdin);
int t;
scanf("%d",&t);
int ca=0;
while (t--)
solve(++ca);
return 0;
}
参考了以下文章,在此表示感谢:
https://www.cnblogs.com/fan-jiaming/p/10047017.html