CS Academy Distinct Neighbours(经典dp)

CS Academy Distinct Neighbours(经典dp)

题意:

求相邻无相同数字的合法的排列数

题解:

题解

先将相同的数字分为一类,假设共有n组
定义\(dp[i][j]\)表示前i组数字恰好有j对相邻数字相同的方案数,那么最后答案就是dp[n][0]

已经考虑完了前\(i\)组数,现在考虑第\(i+\)组数,如何放置
首先可以枚举放\(k\)个位置,有\(C(cnt[i+1]-1,k-1)\)种放法,然后将这k个位置分成两类
一类放在相同的数字中间 放了\(L\)个位置,有\(C(j,L)\)种那么相邻相同的对数变成\(j - L\)
一类不放相同的数字中间 放\(k - L\) 个位置,有\(C(S - j,k - L)\)
最后第\(i+1\)组数 增加了\(cnt[i+1] - k\)对相邻相同的数,即最后变成\(dp[i+1][j - L + cnt[i+1] - k]\)

四层循环 复杂度\(O(n^{3})\)

#include<bits/stdc++.h>
#define LL long long
using namespace std;
const int N = 800;
const int mod = 1e9 + 7;
int C[N][N];
void init(){
    for(int i = 0;i < N;i++) C[i][0] = C[i][i] = 1;
    for(int i = 2;i < N;i++){
        for(int j = 1;j <= i;j++){
            C[i][j] = (C[i-1][j] + C[i-1][j-1])%mod;
        }
    }
}
int dp[N][N],cnt[N],total[N];
vector<int> v;
int main(){

    init();
    int n,x,mx = 1;
    cin>>n;
    v.push_back(0);
    for(int i = 1;i <= n;i++){
        cin>>x;
        if(!cnt[x]) v.push_back(x);
        cnt[x]++;
    }
    for(int i = 1;i < v.size();i++) total[i] = total[i-1] + cnt[v[i]];
    dp[0][0] = 1;
    for(int i  = 0;i < v.size() - 1;i++){
        int num = cnt[v[i+1]],S = total[i]+1;
        for(int j = S-1;j >= 0;j--){///j对不同
            int kk = min(num,S);///kk个位置可选择
            for(int k = 1;k <= kk;k++){
                int L = min(j,k);
                for(int l = L;S - j >= k - l;l--){
                    int &res = dp[i+1][j - l + num - k];
                    res = (res + 1LL * C[num - 1][k - 1] * C[j][l] % mod * C[S-j][k-l]%mod * dp[i][j]%mod)%mod;
                }
            }
        }
    }
    cout<<dp[v.size()-1][0]<<endl;
    return 0;
}

听说还有\(O(n^{2})\)的做法 用到了下面这个东西,研究一下再写写

\(n_1\)\(a_1\),\(n_2\)\(a_2\),...\(n_r\)\(a_r\)的相邻无相同的排列方法数
\[f(n_1,n_2,...,n_r) = \sum_{1<=t_i<=n_i}^{r}\coprod_{i=1}^{r}(-1)^{n_i-t_i}\binom{n_i-1}{t_i-1}\frac{(t_1+t_2+...+t_r)!}{t_1!t_2!...t_r!}\]

转载于:https://www.cnblogs.com/jiachinzhao/p/7410938.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值