BZOJ1931: [Shoi2007]Permutation 有序的计数

138 篇文章 0 订阅
11 篇文章 0 订阅

设给定的排列为T,题中所给符号用f(T)表示(懒得打
枚举排列S
枚举S从第i+1位开始比T小,那么S的前i位和T相同,设前i位得到的价值为c,i+1~n位就需要提供f(T)-c的价值
设i+1~n中有k个数没有在T的1~i位出现,则S的i+1~n位只能由这k个数中的f(T)-c个数提供价值,带上一个 Cf(T)ck 的系数
那么剩下的 k(f(T)c) 个数在S中不能在自己的位置,其余的数任意放
问题就变成有x+y个数,其中x个数不能在自己的位置,y个数任意放的方案数
可以容斥, xi=0(1)iCix(x+yi)!

不取模要写高精度

code:

#include<set>
#include<map>
#include<deque>
#include<queue>
#include<stack>
#include<cmath>
#include<ctime>
#include<bitset>
#include<string>
#include<vector>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<climits>
#include<complex>
#include<iostream>
#include<algorithm>
#define ll long long
using namespace std;

const int maxn = 70;
const int maxl = 200;

struct bignum
{
    int a[maxl],len;
    bignum(){memset(a,0,sizeof a);len=1;}
    friend inline bignum operator +(bignum x,const bignum &y)
    {
        int l=max(x.len,y.len);
        for(int i=1;i<=l;i++)
        {
            x.a[i]+=y.a[i];
            if(x.a[i]>9) x.a[i]-=10,x.a[i+1]++;
        }
        if(x.a[l+1]) l++;
        x.len=l;
        return x;
    }
    friend inline bignum operator -(bignum x,bignum y)
    {
        int l=x.len;
        for(int i=1;i<=l;i++)
        {
            x.a[i]-=y.a[i];
            if(x.a[i]<0) x.a[i+1]--,x.a[i]+=10;
        }
        while(!x.a[l]&&l>0) l--;
        x.len=l;
        return x;
    }
    friend inline bignum operator *(bignum x,int k)
    {
        int l=x.len;
        for(int i=l;i>=1;i--) x.a[i]*=k;
        for(int i=1;i<=l;i++) x.a[i+1]+=x.a[i]/10,x.a[i]%=10;
        while(x.a[l+1])
        {
            l++;
            x.a[l+1]+=x.a[l]/10,x.a[l]%=10;
        }
        x.len=l;
        return x;
    }
    friend inline bignum operator *(bignum &x,bignum &y)
    {
        bignum re;
        int l1=x.len,l2=y.len;
        for(int i=1;i<=l1;i++) for(int j=1;j<=l2;j++)
            re.a[i+j-1]+=x.a[i]*y.a[j];
        int l=x.len+y.len-1;
        for(int i=1;i<=l;i++)
            re.a[i+1]+=re.a[i]/10,re.a[i]%=10;

        while(re.a[l+1])
        {
            l++;
            re.a[l+1]+=re.a[l]/10,re.a[l]%=10;
        }
        bool empty=true;
        for(int i=1;i<=l;i++) if(re.a[i]) { empty=false;break; }
        if(empty) l=1;
        re.len=l;
        return re;
    }
}f[maxn],C[maxn][maxn],Ans;
int n;

bool use[maxn];
int a[maxn],sum;

bignum g(int x,int y)
{
    bignum r1,r2,tmp;
    for(int i=0;i<=x;i+=2)
    {
        tmp=C[x][i]*f[x+y-i];
        r1=r1+tmp;
    }
    for(int i=1;i<=x;i+=2)
    {
        tmp=C[x][i]*f[x+y-i];
        r2=r2+tmp;
    }
    r1=r1-r2;
    return r1;
}
void cal(int now)
{
    int s=0;
    for(int i=1;i<=now;i++) s+=a[i]==i;
    if(s>sum) return;

    int dec=sum-s,m=n-now,ok=0;
    for(int i=now+1;i<=n;i++) if(!use[i]) ok++;
    if(ok<dec) return;

    bignum delta=g(ok-dec,m-ok); delta=delta*C[ok][dec];
    Ans=Ans+delta;
    return;
}

int main()
{
    f[0].a[1]=1,f[0].len=1;
    for(int i=1;i<maxn;i++) f[i]=f[i-1]*i;

    C[0][0].a[1]=C[0][0].len=1;
    for(int i=1;i<maxn;i++)
    {
        C[i][0].a[1]=C[i][0].len=1;
        for(int j=1;j<=i;j++) C[i][j]=C[i-1][j-1]+C[i-1][j];
    }

    scanf("%d",&n);
    for(int i=1;i<=n;i++) 
    {
        scanf("%d",&a[i]),a[i]++;
        sum+=a[i]==i;
    }

    for(int i=1;i<=n;i++)
    {
        int t=a[i];
        for(int j=1;j<t;j++) if(!use[j])
            use[a[i]=j]=true,cal(i),use[j]=false;
        use[a[i]=t]=true;
    }

    printf("%d ",sum);
    for(int i=Ans.len;i>=1;i--) printf("%d",Ans.a[i]); putchar('\n');

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值