[JZOJ5086]数列

本文介绍了一种解决特定组合数学问题的算法——计算给定条件下逆序对数量的方法。通过对排列进行预处理并利用meet-in-the-middle技巧,该算法能在合理的时间复杂度内求解。适用于竞赛编程中涉及逆序对计数的问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目大意

有一个长度为n的排列,但是有一些位置的数字还没有确定。你需要统计逆序对为K的可能的排列个数。

1n103,1K109
保证没有确定的位数m不超过14


题目分析

既然没有确定的位数最多只有14,那么考虑meet in middle。
搜出后面位置填了什么,然后将其用随便一种方式存下来。
然后搜出前面的位置填的数,然后在前面存下来的那里找到对应于没有出现过的数的状态,瞎合并一下。
如果你各种细节实现得好一点,你是可以做到O(m!(m/2)!×m2)的。


代码实现

#include <algorithm>
#include <iostream>
#include <cstdio>

using namespace std;

typedef long long LL;

const int N=1005;
const int L=14;
const int C=N*L/2;
const int S=3500;
const int P=67;
const int A=1<<L;

int pre[N][N],suf[N][N],tot[N];
int per[N],rem[N],pos[N],p[N];
bool exist[N],used[N];
int f[S][C];
int id[A];
int n,K,cnt,l,cur;
LL ans;

int lowbit(int x){return x&-x;}

int bitcount(int s)
{
    int ret=0;
    for (;s;s-=lowbit(s)) ++ret;
    return ret;
}

void prepare()
{
    for (int i=1;i<=n;++i)
    {
        for (int j=1;j<=n;++j) pre[i][j]=pre[i-1][j];
        if (per[i]) ++pre[i][per[i]];
    }
    for (int i=1;i<=n;++i)
        for (int j=n;j>=1;--j)
            pre[i][j]+=pre[i][j+1];
    for (int i=n;i>=1;--i)
    {
        for (int j=1;j<=n;++j) suf[i][j]=suf[i+1][j];
        if (per[i]) ++suf[i][per[i]];
    }
    for (int i=1;i<=n;++i)
        for (int j=1;j<=n;++j)
            suf[i][j]+=suf[i][j-1];
    for (int i=1;i<=n;++i)
        if (per[i]) cur+=pre[i][per[i]+1];
    l=pos[0]/2;
    for (int s=0;s<1<<pos[0];++s)
        if (bitcount(s)==pos[0]-l)
            id[s]=++cnt;
}

int tmp[20],rec[20];

void load(int curr)
{
    int s=0;
    for (int i=l+1;i<=pos[0];++i) s|=1<<p[i]-1;
    int sid=id[s],ret=curr;
    for (int i=l+1;i<=pos[0];++i) ret+=pre[pos[i]][rem[p[i]]+1]+suf[pos[i]][rem[p[i]]-1];
    tmp[0]=0,rec[0]=0;
    for (int i=1;i<=pos[0];++i)
        if (!((s>>i-1)&1)) tmp[++tmp[0]]=rem[i];
        else rec[++rec[0]]=rem[i];
    sort(tmp+1,tmp+1+tmp[0]),sort(rec+1,rec+1+rec[0]);
    for (int i=1,ptr=1;i<=rec[0];++i)
    {
        for (;ptr<=tmp[0]&&tmp[ptr]<rec[i];++ptr);
        ret+=tmp[0]+1-ptr;
    }
    ++f[sid][ret];
}

void dfs(int x,int curr)
{
    if (x==pos[0]+1)
    {
        load(curr);
        return;
    }
    int total=0;
    for (int i=1;i<=rem[0];++i)
        if (!used[i]) used[i]=1,p[x]=i,dfs(x+1,curr+x-l-1-total),used[i]=0;
        else ++total;
}

void solve(int curr)
{
    int s=0;
    for (int i=1;i<=l;++i) s|=1<<p[i]-1;
    int sid=id[((1<<pos[0])-1)^s],ret=curr;
    for (int i=1;i<=l;++i) ret+=pre[pos[i]][rem[p[i]]+1]+suf[pos[i]][rem[p[i]]-1];
    if (K-cur-ret>=0&&K-cur-ret<=n*(pos[0]-l)) ans+=f[sid][K-cur-ret];
}

void calc(int x,int curr)
{
    if (x==l+1)
    {
        solve(curr);
        return;
    }
    int total=0;
    for (int i=1;i<=rem[0];++i)
        if (!used[i]) used[i]=1,p[x]=i,calc(x+1,curr+x-1-total),used[i]=0;
        else ++total;
}

int main()
{
    freopen("sequence.in","r",stdin),freopen("sequence.out","w",stdout);
    scanf("%d%d",&n,&K);
    for (int i=1;i<=n;++i)
    {
        scanf("%d",&per[i]);
        if (!per[i]) pos[++pos[0]]=i;
        else exist[per[i]]=1;
    }
    for (int i=1;i<=n;++i) if (!exist[i]) rem[++rem[0]]=i;
    prepare(),dfs(l+1,0),calc(1,0);
    printf("%lld\n",ans);
    fclose(stdin),fclose(stdout);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值