zoukankan      html  css  js  c++  java
  • 同色不相邻的方案数求解

    同色不相邻的方案数求解

    引例:

    (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)段,一共(inom{a_i-1}{k-1})种方法。
    这一共造成了(a_i - k)个新的不合法情况。
    枚举把其中(l)块插入原先的不合法位置之间,那么一共消除了(l)个不合法相邻。
    这一共有(inom{j}{l})种选择,剩下的块有(inom{sum + 1 - j}{k - l})种选择。
    所以转移方程式:

    [f_{i,j-l+(a_i-k)} = sum f_{i,j} inom{a_i-1}{k-1} inom{j}{l} inom{sum + 1 - j}{k - l} ]

    其中(sum = sum_{e=1}^{i-1} a_e)
    满复杂度:(O(n(sum a)a_{max}^2)),但是非常不满,实测跑 (n,sum aleq 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) = inom{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
    " , ans) ;  return 0;
    }
    

  • 相关阅读:
    webpack基础
    LeetCode232. 用栈实现队列做题笔记
    mysql 时间加减一个月
    leetcode 1381. 设计一个支持增量操作的栈 思路与算法
    LeetCode 141. 环形链表 做题笔记
    leetcode 707. 设计链表 做题笔记
    leetcode 876. 链表的中间结点 做题笔记
    leetcode 143. 重排链表 做题笔记
    leetcode 1365. 有多少小于当前数字的数字 做题笔记
    LeetCode1360. 日期之间隔几天 做题笔记
  • 原文地址:https://www.cnblogs.com/GuessYCB/p/8726819.html
Copyright © 2011-2022 走看看