Description
有一个长度为n 的排列,现在有一些位置的数已经模糊不清了,你只知道这个排列的逆序对个数是K,你能计算出总共有多少可能的排列吗?
Input
第一行两个整数n,K。
接下来一行n 个0 到n 的数字,保证1 到n 最多出现一次。0 表示这个数看不清了。
Output
输出一个整数表示答案。
Sample Input
5 5
4 0 0 2 0
Sample Output
2
Data Constraint
对于10% 的数据,0 的个数不超过10 个。
对于另10% 的数据,0 出现的位置为连续一段。
对于100% 的数据,n <=10^3,K<=10^9,0 的个数不超过14。
题解
注意到0的个数只多了一点点,所以应该还是暴力,但是加了一些神奇的优化
显然不是普通的剪枝(剪到几十万分之一?)
考虑折半搜索
先枚举一下前面的一半选择哪一些数
然后做完前面的一半,然后用一个桶存一下不包括后面一半0的每种逆序对个数的数量,然后再做第二次dfs搞一下后面一半的排列方式,就可以用桶计算答案了
贴代码
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<map>
#define ll long long
using namespace std;
const int maxn=1005;
ll tong[8005];
int cc1[20],cc2[20];
int a[maxn],dui[maxn],p[maxn];
bool bz[maxn];
int ad[maxn][maxn];
int i,j,k,l,m,n,x,y,z,mm,t1,t2,cs,cq;
ll ans;
void dfs(int x,int y,int z){
int i,cc;
if (z>m) return;
if (x==l/2+1){
tong[z]++;
} else{
cc=0;
for (i=l/2;i>=1;i--)
if (((1<<(i-1)) & y)==0){
dfs(x+1,y+(1<<(i-1)),z+cc+ad[x][cc1[i]]);
} else cc++;
}
}
void dfs1(int x,int y,int z){
int i,cc;
if (z>mm) return;
if (x==cs+1){
ans+=tong[mm-z];
} else{
cc=0;
for (i=cs;i>=1;i--)
if (((1<<(i-1)) & y)==0){
dfs1(x+1,y+(1<<(i-1)),z+cc+ad[cq+x][cc2[i]]);
} else cc++;
}
}
void dfss(int x,int y){
if (x>l && y==l/2){
memset(tong,0,sizeof(tong));
dfs(1,0,0);
z=0; t2=1; t1=1;
for (i=1;i<=l;i++){
if(cc1[t1]==i){
z+=t2-1;
t1++;
if (t1==cq+1) break;
} else t2++;
}
mm=m-z;
cs=l-l/2;
dfs1(1,0,0);
} else{
if (x>l) return;
cc1[y+1]=x;
dfss(x+1,y+1);
cc2[x-y]=x;
dfss(x+1,y);
}
}
int main(){
freopen("sequence.in","r",stdin);
freopen("sequence.out","w",stdout);
scanf("%d%d",&n,&m);
for (i=1;i<=n;i++){
scanf("%d",&a[i]);
if (a[i]==0) dui[++l]=i;
bz[a[i]]=true;
}
for (i=1;i<=n;i++)
if (bz[i]==false) p[++x]=i;
for (i=1;i<=l;i++){
for (k=1;k<=l;k++){
x=p[k];
if (dui[i]>1)
for (j=dui[i]-1;j>=1;j--) if (a[j]>x) ad[i][k]++;
for (j=dui[i]+1;j<=n;j++) if ((a[j]) && (x>a[j])) ad[i][k]++;
}
}
for (i=1;i<=n-1;i++)
if (a[i]){
for (j=i+1;j<=n;j++) if (a[i]>a[j] && a[j])m--;
}
cq=l/2;
dfss(1,0);
printf("%lld\n",ans);
return 0;
}