1. 程式人生 > >【XSY3156】簡單計數II 容斥 DP

【XSY3156】簡單計數II 容斥 DP

不為 std def ans amp sum nom pro 假設

題目大意

  定義一個序列的權值為:把所有相鄰的相同的數合並為一個集合後,所有集合的大小的乘積。

  特別的,第一個數和最後一個數是相鄰的。

  現在你有 \(n\) 種數,第 \(i\) 種有 \(c_i\) 個。求所有不同的序列的權值的和。

  \(n\leq 50,c_i\leq 100\)

題解

  考慮第一個數和最後一個數不相鄰時怎麽做。

  記 \(g_{i,j}\) 為出現了 \(i\) 次的數分成 \(j\) 個集合,所有集合大小的乘積的和。
\[ g_{i,j}=\sum_{k=1}^ig_{i-k,j-1} \]
  假設最後 \(i\) 分成了 \(a_i\) 個集合,那麽答案就是 \(\prod_{i=1}^ng_{c_i,a_i}\)

再乘上方案數。

  方案數可以容斥求。

  具體來說,把最後相鄰且同色的球合並成一個大球。設最後有 \(b_i\) 個大球,那麽容斥的系數就是 \(\binom{a_i-1}{b_i-1}{(-1)}^{a_i-b_i}\)

  最後這 \(\sum b_i\) 個球可以隨意放,方案數是 \(\frac{(\sum b_i)!}{\prod b_i!}\)

  總的答案是
\[ (\prod_{i=1}^ng_{c_i,a_i}\binom{a_i-1}{b_i-1}{(-1)}^{a_i-b_i})\frac{(\sum_{i=1}^nb_i)!}{\prod_{i=1}^n b_i!} \]


  這樣就可以 DP 了。(狀態為 \(i\)\(\sum b_i\)

  考慮第一個數和最後一個數相鄰時怎麽做。

  可以用最小表示法,令第一個數為 \(1\) 且 最後一個數不為 \(1\)

  只需要在後面計算組合數的時候把 \(b_1-1\) 再除以 \(a_1\) 就可以得到第一個數為 \(1\) 的方案數。

  把 \(b_1-2\) 就可以得到第一個數為 \(1\) 再除以 \(a_1\) 且最後一個數也是 \(1\) 的方案數。

  除以 \(a_1\) 是因為一個方案會被算多次。

  再把方案數乘以 \(\sum c_i\) 就是答案了。

  時間復雜度:\(O((\sum c_i)^2)\)

代碼

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const ll p=1000000007;
ll fac[5010],ifac[5010],inv[5010];
ll f[60][5010];
ll g[110][110];
int a[60];
int n;
int s[60];
ll c[110][110];
ll c1[110],c2[110];
ll binom(int x,int y)
{
    return x>=y&&y>=0?fac[x]*ifac[y]%p*ifac[x-y]%p:0;
}
int main()
{
#ifndef ONLINE_JUDGE
    freopen("c.in","r",stdin);
    freopen("c.out","w",stdout);
#endif
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
        s[i]=s[i-1]+a[i];
    }
    inv[1]=fac[0]=fac[1]=ifac[0]=ifac[1]=1;
    for(int i=2;i<=5000;i++)
    {
        inv[i]=-p/i*inv[p%i]%p;
        fac[i]=fac[i-1]*i%p;
        ifac[i]=ifac[i-1]*inv[i]%p;
    }
    g[0][0]=1;
    for(int i=1;i<=100;i++)
        for(int j=1;j<=100;j++)
            for(int k=1;k<=i;k++)
                g[i][j]=(g[i][j]+g[i-k][j-1]*k)%p;
    f[0][0]=1;
    for(int i=1;i<n;i++)
        for(int j=1;j<=a[i];j++)
            for(int k=1;k<=j;k++)
                c[i][k]=(c[i][k]+g[a[i]][j]*binom(j-1,k-1)%p*((j-k)&1?-1:1))%p;
    for(int i=n;i<=n;i++)
        for(int j=1;j<=a[i];j++)
            for(int k=1;k<=j;k++)
                c1[k]=(c1[k]+g[a[i]][j]*binom(j-1,k-1)%p*((j-k)&1?-1:1)*inv[j])%p;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=a[i];j++)
            for(int k=0;k<=s[i-1];k++)
                f[i][k+j]=(f[i][k+j]+f[i-1][k]*c[i][j]%p*binom(k+j,k))%p;
    ll ans=0;
    for(int j=1;j<=a[n];j++)
        for(int k=0;k<=s[n-1];k++)
            ans=(ans+f[n-1][k]*c1[j]%p*(binom(k+j-1,k)-binom(k+j-2,k)))%p;
    ans=ans*s[n]%p;
    ans=(ans+p)%p;
    printf("%lld\n",ans);
    return 0;
}

【XSY3156】簡單計數II 容斥 DP