zoukankan      html  css  js  c++  java
  • 洛谷4859 BZOJ3622 已经没什么好害怕的了(DP,二项式反演)

    题目链接:

    洛谷

    BZOJ

    题目大意:有两个长为 $n$ 的序列 $a,b$,问有多少种重排 $b$ 的方式,使得满足 $a_i>b_i$ 的 $i$ 的个数比满足 $a_i<b_i$ 的 $i$ 的个数恰好多 $k$ 个。答案对 $10^9+9$ 取模。

    $1le nle 2000,0le kle n$。保证 $a,b$ 中没有相同的数。


     首先根据小学数学知识可知,$a_i>b_i$ 的个数应该是 $frac{n+k}{2}$。如果 $n+k$ 不是偶数那么就无解。

    那么就可以DP了。首先将 $a$ 和 $b$ 分别排序,令 $c_i$ 表示 $b$ 中 $<a_i$ 的数的个数。

    设 $dp_{i,j}$ 表示在前 $i$ 个 $a$ 中,选了 $j$ 个 $a$ 和 $j$ 个 $b$ 并凑出了恰好 $j$ 对满足 $a>b$ 的对的方案数,那么有:

    $dp_{i,0}=1$

    $dp_{i,j}=dp_{i-1,j}+dp_{i-1,j-1}(c_i-j+1)$

    解释一下第二句,前半部分是表示 $a_i$ 不找,后半部分表示 $a_i$ 找。本来可以有 $c_i$ 个 $b$ 可以选,但是前面 $j-1$ 个 $i$ 已经选了 $j-1$ 个了。又因为 $a$ 从小到大,所以还可以选 $c_i-j+1$ 个。

    令 $f_k$ 为恰好 $k$ 对数的答案, $g_k$ 为至少 $k$ 对数的答案。

    我们发现 $g_k$ 比较好算,$g_k=dp_{n,k}(n-k)!$。因为选出了 $k$ 对之后,剩下的可以随便搭配。

    然后又可以发现 $g_k=sumlimits^n_{i=k}{ichoose k}f_i$。为什么???因为这题标签是二项式反演啊……(smg……)

    好吧,我不会证,直接用吧(逃

    那么 $f_k=sumlimits^n_{i=k}(-1)^{i-k}{ichoose k}g_i$。

    时间复杂度 $O(n^2)$。


    upd:问了PBdalao为什么是组合数,把他的话放这吧:

    对于 $j$ 的一个方案,它在 $i$ 中必然是这么统计的:有 $i$ 个是DP得到的,另外 $j-i$ 个是后面乱搞得到的。

    所以 $j$ 中每一组 $i$ 都被统计了一次,最后就被统计了 $jchoose i$ 次。

    (很有道理,不是吗?)


    代码:

    #include<bits/stdc++.h>
    using namespace std;
    const int maxn=2222,mod=1000000009;
    #define FOR(i,a,b) for(int i=(a);i<=(b);i++)
    #define ROF(i,a,b) for(int i=(a);i>=(b);i--)
    #define MEM(x,v) memset(x,v,sizeof(x))
    inline int read(){
        char ch=getchar();int x=0,f=0;
        while(ch<'0' || ch>'9') f|=ch=='-',ch=getchar();
        while(ch>='0' && ch<='9') x=x*10+ch-'0',ch=getchar();
        return f?-x:x;
    }
    int n,k,a[maxn],b[maxn],c[maxn],dp[maxn][maxn],fac[maxn],inv[maxn],invfac[maxn];
    inline int C(int n,int m){
        return 1ll*fac[n]*invfac[m]%mod*invfac[n-m]%mod;
    }
    inline int g(int x){
        return 1ll*dp[n][x]*fac[n-x]%mod;
    }
    inline int f(int x){
        int ans=0;
        FOR(i,x,n){
            int v=1ll*C(i,x)*g(i)%mod;
            if((i-x)&1) ans=(ans-v+mod)%mod;
            else ans=(ans+v)%mod;
        }
        return ans;
    }
    int main(){
        n=read();k=read();
        if((n+k)&1) return puts("0"),0;
        k=(n+k)>>1;
        fac[0]=fac[1]=inv[1]=invfac[0]=invfac[1]=1;
        FOR(i,2,n){
            fac[i]=1ll*fac[i-1]*i%mod;
            inv[i]=mod-1ll*(mod/i)*inv[mod%i]%mod;
            invfac[i]=1ll*invfac[i-1]*inv[i]%mod;
        }
        FOR(i,1,n) a[i]=read();
        FOR(i,1,n) b[i]=read();
        sort(a+1,a+n+1);sort(b+1,b+n+1);
        int cur=1;
        FOR(i,1,n){
            while(cur<=n && b[cur]<a[i]) cur++;
            c[i]=cur-1;
        }
        dp[0][0]=1;
        FOR(i,1,n){
            dp[i][0]=dp[i-1][0];
            FOR(j,1,i) dp[i][j]=(dp[i-1][j]+1ll*dp[i-1][j-1]*(c[i]-j+1))%mod;
        }
        printf("%d
    ",f(k));
    }
    二项式反演
  • 相关阅读:
    [SQL Server] 数据库日志文件自动增长导致连接超时的分析
    Visual Studio 2003/Visual Studio 2005常用快捷键(快捷方式)
    設置RichTextBox控件中選中的部分的文字突顯
    電功率單位
    一名25岁的董事长给大学生的18条忠告
    計算機端口
    學習使用Directionary與Hastable
    VS .NET如何切換環境
    VS .NET(C#)如何動態創建控件
    VS .NET(C#)測試程序記錄時間方法
  • 原文地址:https://www.cnblogs.com/1000Suns/p/10354081.html
Copyright © 2011-2022 走看看