题目描述
牛牛的作业薄上有一个长度为 n 的排列 A,这个排列包含了从1到n的n个数,但是因为一些原因,其中有一些位置(不超过 10 个)看不清了,但是牛牛记得这个数列顺序对的数量是 k,顺序对是指满足 i < j 且 A[i] < A[j] 的对数,请帮助牛牛计算出,符合这个要求的合法排列的数目。
输入描述
每个输入包含一个测试用例。每个测试用例的第一行包含两个整数 n 和 k(1 <= n <= 100, 0 <= k <= 1000000000),接下来的 1 行,包含 n 个数字表示排列 A,其中等于0的项表示看不清的位置(不超过 10 个)。
输出描述
输出一行表示合法的排列数目。
解题思路
这道题最直接的做法是暴力搜索,10!种情况,可以尝试数据量,在处理暴力搜索的每一种情况时,已经给出的数据的顺序对会重复计算,所以可以预先将已经给定的数据的顺序对求解出来。而没有给出的数据,最多是10个数,需要计算出这些数据本身的顺序对和这些数据和已经给出的数据的之间的顺序对,这三部分的和就是结果。
对于没有给出的数据之间的顺序对,每一种排列对应一种情况。而对于没有给出的数据和已经给出的数据之间的顺序,由于没有给出的数据的位置是不确定的,直接的做法是在每一种排列中,计算没有给出的数据之间的顺序对时,同时计算和已经给出的数据之间的顺序对。
而从另一个角度想,因为数据的位置是不确定的,每一个空缺的位置都是可能的,所以可以预先计算出每一个缺失的数据在每一个空缺位置上的顺序对(该位置前比它小的已经给出的数据的数量,该位置后比它大的已经给出的数据的数量),那么在每一种排列中,只需要根据该数值和它的位置就可以直接确定出这个数和已经给出的数据之间的顺序对。
主要使用数据预处理来减少数据量。
代码实现
#include <stdio.h>
#include <algorithm>
using namespace std;
int a[105];
int missidx[15];
int missnum[15];
int smaller[105][105];
int larger[105][105];
int appear[105];
int getSeqNum(int* data,int n){
int cnt=0;
for(int i=1;i<n;i++){
if(!data[i])
continue;
for(int j=0;j<i;j++){
if(data[j]&&data[j]<data[i])
cnt++;
}
}
return cnt;
}
int main(){
int n,k,i,j;
scanf("%d%d",&n,&k);
int misscnt=0;
for(i=0;i<n;i++){
scanf("%d",&a[i]);
if(!a[i]) missidx[misscnt++]=i;
else appear[a[i]]=1;
}
misscnt=0;
for(i=1;i<=n;i++)
if(!appear[i])
missnum[misscnt++]=i;
int given=getSeqNum(a,n);
if(given>k){
printf("0\n");
return 0;
}
int small,large;
for(i=0;i<misscnt;i++){
small=0;large=0;
for(j=0;j<n;j++){
if(!a[j])
smaller[j][missnum[i]]=small;
else if(a[j]<missnum[i]) small++;
}
for(j=n-1;j>=0;j--){
if(!a[j])
larger[j][missnum[i]]=large;
else if(a[j]>missnum[i]) large++;
}
}
int res=0;
do{
int inner=getSeqNum(missnum,misscnt);
for(i=0;i<misscnt;i++){
inner+=smaller[missidx[i]][missnum[i]];
inner+=larger[missidx[i]][missnum[i]];
}
if(inner+given==k)
res++;
}while(next_permutation(missnum,missnum+misscnt));
printf("%d\n",res);
return 0;
}