1. 程式人生 > >同色不相鄰的方案數求解

同色不相鄰的方案數求解

const turn 理解 方案 網址 mark pla 列數 方便

同色不相鄰的方案數求解

引例:

\(n\)種顏色的小球,
每種顏色的小球有\(a_i\)個,即一共有\(\sum_{i=1}^n a_i\)個小球。
現在要求把這些小球排同色不相鄰的方案數求解成一行,要求同種顏色的小球不相鄰
求方案數,答案對\(10^9+7\)取模。 提交網址:web

前言

下面的算法都只考慮 同色小球之間無區別 的方案數。
可重排列的情況。
如果同色小球之間有區別,為不重排列,那麽對應乘上 \(\prod (a_i!)\) 即可。

方法1:動態規劃

解法

從前往後枚舉每種顏色的小球。
\(f_{i,j}\)表示考慮完前\(i\)種小球,存在\(j\)違法相鄰的方案數。

轉移首先考慮將\(a_i\)個小球分為\(k\)段,一共\(\binom{a_i-1}{k-1}\)種方法。
這一共造成了\(a_i - k\)個新的不合法情況。
枚舉把其中\(l\)塊插入原先的不合法位置之間,那麽一共消除了\(l\)個不合法相鄰。
這一共有\(\binom{j}{l}\)種選擇,剩下的塊有\(\binom{sum + 1 - j}{k - l}\)種選擇。
所以轉移方程式:
\[f_{i,j-l+(a_i-k)} = \sum f_{i,j} \binom{a_i-1}{k-1} \binom{j}{l} \binom{sum + 1 - j}{k - l} \]
其中\(sum = \sum_{e=1}^{i-1} a_e\)

滿復雜度:\(O(n(\sum a)a_{max}^2)\),但是非常不滿,實測跑 \(n,\sum a\leq 1000\)並不虛。
這種方法好寫好理解,但是適用性較窄(不方便添加整段權值)。

實現代碼

#include<bits/stdc++.h>
#define ll long long
#define maxn 2005
#define mod 1000000007
using namespace std;

ll n , q, x , sum , cur;
ll a[maxn] , fac[maxn] , c[maxn][maxn] , f[3][maxn];

void Pre(){
    fac[0] = 1;
    for(int i = 1; i <= n; i ++)fac[i] = 1ll*i*fac[i-1] % mod;
    c[0][0] = 1;
    for(int i = 1; i <= n; i ++){
        for(int j = 1; j < i; j ++)
            c[i][j] = ( c[i-1][j] + c[i-1][j-1] ) % mod;
        c[i][0] = c[i][i] = 1;
    }
    return;
}

int main(){
    cin >> n >> q;
    for(int i = 1; i <= n; i ++) a[i] = gi();
    Pre();
    sum = 0; cur = 0;
    f[cur][0] = 1;
    for(int i = 1; i <= n; i ++){
        cur ^= 1;
        for(int t = 0; t <= a[i]+sum+1; t ++)
            f[cur][t] = 0;
        for(int k = 1; k <= a[i] && k <= sum+1; k ++)
            for(int j = 0; j <= sum; j ++)
                for(int l = 0; l <= k && l <= j; l ++){                  
                    f[cur][j-l+a[i]-k] +=
                        1ll*f[cur^1][j]*c[a[i]-1][k-1]%mod*(1ll*c[j][l]*c[sum+1-j][k-l]%mod)%mod;
                    f[cur][j-l+a[i]-l] %= mod;
                }
        sum += a[i];
    }
    cout<<f[cur][0]; return 0;
}

方法2:容斥原理

解法

全部不相鄰的方案數 = 全部不相臨的排列數 - 1個相鄰的排列數 + 2個相鄰的排列數 ......
直接枚舉不好算,單獨考慮一種小球的貢獻。
由於可重排列:\(P = \frac{(\sum a)!}{ \prod (a!)}\),所以先只乘\(\frac{1}{a!}\),最後乘上\((\sum a)!\)即可。
我們可以列出有\(a_i\)個小球的容斥多項式:
\[f(a_i) = \sum_{i = 1} ^ {a_i} (-1)^{a_i-i} \frac{1}{i!}E(a_i , i)\]
其中\(E(i,j)\)表示\(i\)個小球因為違法相鄰而實際只有\(j\)段的方案數。
顯然就是要保留\(i-1\)個間隔中的\(j-1\)個,所以\(E(i,j) = \binom{i-1}{j-1}\)
把每種小球的容斥多項式卷積卷起來就可以得到總的容斥式。
對於最後的總容斥式的每一項,乘上對應的階乘(可重排列的分子),再加起來即可。
總時間復雜度:\(O(n(\sum a)a_{max})\)
這種做法較難理解、容易寫錯,但是復雜度低,適用性好(添加權值直接在計算系數時乘上即可)。

實現代碼

//http://acm.hdu.edu.cn/showproblem.php?pid=4532
#include<bits/stdc++.h>
#define RG register
#define IL inline
#define N 505 
using namespace std;
const int mod = 1000000007 ; 
int dp[10*N],C[N+5][N+5],E[N+5][N+5],fact[N+5],a[N+5],n,sum,ans;
int inv[N+5],inv_fact[N+5],coef[N+5]; 

IL void Pre(){
    C[0][0] = 1;
    for(RG int i = 1,j; i <= N; i ++)
        for(j = C[i][0] = 1; j <= i; j ++)
            C[i][j] = (C[i-1][j-1] + C[i-1][j]) % mod ;
    for(RG int i = 1,e; i <= N; i ++)
        for(RG int j = i,e = 1; j >= 1; j --)
            E[i][j] = (mod + C[i-1][j-1] * e) % mod, e = -e ;
    fact[0] = inv[0] = 1 ;
    inv[1] = inv_fact[0] = 1 ;
    for(RG int i = 1; i <= N; i ++){
        fact[i] = 1ll * i * fact[i-1] % mod;
        if(i ^ 1)inv[i] = 1ll * (mod - mod/i) * inv[mod % i] % mod;
        inv_fact[i] = 1ll * inv_fact[i-1] * inv[i] % mod ; 
    }return ; 
}

IL void upt(RG int &x,RG int y){x += y; if(x>=mod) x-= mod ; }
int main(){
    Pre() ;
    scanf("%d",&n) ; 
    sum = 0; dp[0] = 1 ;
    for(RG int i = 1; i <= N; i ++) dp[i] = 0;
    for(RG int sq = 1; sq <= n; sq ++){
        scanf("%d",&a[sq]) ;  // 有一種小球的個數為a個.
        for(RG int k = 1; k <= a[sq]; k ++)
            coef[k] = 1ll * E[a[sq]][k] * inv_fact[k] % mod ;
        //coef是 當前這個小球的容斥多項式 的每一項的系數
        for(RG int j = sum; j >= 0; j --){
            RG int tmp = dp[j] ;
            dp[j] = 0;
            for(RG int k = 0; k <= a[sq]; k ++)
                upt(dp[j + k] , 1ll * coef[k] * tmp % mod) ; 
        }
        sum += a[sq];       //dp記錄的是最後的總容斥式的每一項系數.
    }
    ans = 0;
    for(RG int k = 0; k <= sum; k ++) upt(ans , 1ll * dp[k] * fact[k] % mod) ;
    printf("%d\n" , ans) ;  return 0;
}

同色不相鄰的方案數求解