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}inom{n_i-1}{t_i-1}frac{(t_1+t_2+...+t_r)!}{t_1!t_2!...t_r!}
]