【GDOI2017第四轮模拟day1】数列

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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值