【SHOI2009/luogu2159】舞会 DP+容斥+高精度

原题走这里

应该是本人做过的第一道DP+容斥了,容斥原理博大精深啊……

原题问的是至多k对女比男高的情况,我们可以先考虑 至少 k对女比男高的情况,然后用容斥来转化。

首先,让男生女生按身高排序。
d[i][j] d [ i ] [ j ] 为前 i i 个女生中已经确定有j个女生比对应的男生高的情况下,这 j j 个女生的配对情况数,其它女生暂时不考虑
则得出d[i][j]的转移方程:

d[i][j]=d[i1][j]+d[i1][j1](p(j1))

其中, p p 是比第i个女生矮的男生个数。

然后,我们得出了d[n][j],然而这仅仅是比它们的舞伴高的 j j 个女生的配对情况,还有剩下nj对男女生没有考虑。
那么他们该怎么办呢?自由配对不就行了,情况共有 (nj)! ( n − j ) !
于是我们就得到了至少i对女比男高的情况数,姑且设为 f[i] f [ i ]

f[i]=d[n][i](ni)! f [ i ] = d [ n ] [ i ] ∗ ( n − i ) !

然而是错的,上述表达式中,多于k对的情况中有很多是重复的,如下图:
这里写图片描述
其中红色线为原本已确定的女比男高的配对。

因此,我们不能直接通过 f[i]f[i+1] f [ i ] − f [ i + 1 ] 来得到恰好 i i 对的情况,这时候又怎么办呢?如何将多于i对的情况去掉?
通过观察我们可以发现,如上图,一个恰好3对的情况,对 f[2] f [ 2 ] 贡献了 C23=3 C 3 2 = 3 种重复的情况。
以此类推,我们可以发现,一个恰好 i i 对的情况,会对f[j]贡献 Cji C i j 种重复的情况,于是可以得出恰好 i i 对的情况数g[i]

g[i]=f[i]j=i+1nCijg[j] g [ i ] = f [ i ] − ∑ j = i + 1 n C j i ∗ g [ j ]

最后记住要写高精度,包括高精度加法减法,高精度乘int,高精度乘高精度,好恶心啊
具体细节见代码。

代码如下:

#include <bits/stdc++.h>
#define LL long long
#define MOD 100000000
#define N 100
using namespace std;
struct BigI
{
    LL a[N<<1]; 
    bool flag;
    inline LL& operator [](int x) {return a[x];}
    inline BigI(LL n=0)
    {
        memset(this,0,sizeof(*this));
        a[0]=n;
    }   
    BigI friend inline operator+(BigI a1,BigI a2)
    {
        if(a1.flag^a2.flag)
        {
            a2.flag^=1; 
            return a1-a2;
        }
        for(int i=0,j=0;i<N;i++)
        {
            a1[i]+=a2[i]+j;
            j=a1[i]/MOD;
            a1[i]%=MOD;         
        }   
        return a1;
    } 
    BigI friend inline operator*(BigI a1,int n1)
    {
        for(LL i=0,j=0;i<N;i++)
        {
            a1[i]*=n1;
            a1[i]+=j;
            j=a1[i]/MOD;
            a1[i]%=MOD;         
        }           
        return a1;
    }
    BigI friend inline operator*(BigI a1,BigI a2)
    {
        BigI a3;
        a3.flag=a1.flag^a2.flag; 
        for(int i=0;i<N;i++)
        {
            for(int j=0;j<N;j++)
            {
                a3[i+j]=a3[i+j]+a1[i]*a2[j];
            }
        }
        for(int i=0;i<N;i++)
        {
            a3[i+1]+=a3[i]/MOD;
            a3[i]%=MOD;
        }
        return a3;
    }
    BigI friend inline operator-(BigI a1,BigI a2)
    {
        BigI a3(a1); 
        if(a1.flag^a2.flag)
        {
            a2.flag^=1;
            return a1+a2;
        }
        for(int i=0;i<N;i++)
        {
            a1[i]-=a2[i];
            if(a1[i]<0)a1[i+1]--,a1[i]+=MOD;
        }
        if(a1.a[N]<0)
        {
            a1=a2-a3;
            a1.flag^=1;
        }
        return a1; 
    }
    friend void print(BigI a)
    {
        string str="00000000";
        int i=N-1;
        for(;!a[i];i--);
        cout<<a[i];
        for(i--;i>=0;i--)
        {
            str="00000000";
            for(int j=7;j>=0;j--)
            {
                str[j]=a[i]%10+'0';
                a[i]/=10;
            }
            cout<<str;
        } 
        cout<<endl;
    }
}d[201],fac(1),c[201][201],ans;
int n,m,b[210],g[210]; 
int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++)cin>>b[i];
    for(int i=1;i<=n;i++)cin>>g[i];
    sort(b+1,b+n+1);
    sort(g+1,g+n+1); 
    d[0]=BigI(1);
    for(int i=1,k=1;i<=n;i++)
    {
        while(b[k]<g[i]&&k<=n)k++;
        for(int j=i;j;j--)
        {
            d[j]=d[j]+d[j-1]*max(k-j,0);
//            cout<<i<<' '<<j<<" "<<d[j][0]<<endl; 
        }
    }
    for(int i=n;i>=0;i--,fac=fac*(n-i))d[i]=d[i]*fac;
    c[0][0]=BigI(1);
    for(int i=1;i<=n;i++)
    {
        c[i][0]=BigI(1);
        for(int j=1;j<=i;j++)
        {
            c[i][j]=c[i-1][j]+c[i-1][j-1]; 
        }
    }
    for(int i=n;i>=0;i--)
    {
        for(int j=i+1;j<=n;j++)
        {
            d[i]=d[i]-c[j][i]*d[j];
        }
        if(i<=m)ans=ans+d[i];
    }
    print(ans);
    return 0;
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值