统计平衡排列的数目

题目

统计平衡排列的数目


给你一个字符串 num 。如果一个数字字符串的奇数位下标的数字之和与偶数位下标的数字之和相等,那么我们称这个数字字符串是 平衡的

请你返回 num 不同排列 中,平衡 字符串的数目。

由于答案可能很大,请你将答案对 109 + 7 取余 后返回。

一个字符串的 排列 指的是将字符串中的字符打乱顺序后连接得到的字符串。

示例 1:

输入: num = “123”

输出: 2

解释:

  • num 的不同排列包括: "123""132""213""231""312""321"
  • 它们之中,"132""231" 是平衡的。所以答案为 2 。

示例 2:

输入: num = “112”

输出: 1

解释:

  • num 的不同排列包括:"112""121""211"
  • 只有 "121" 是平衡的。所以答案为 1 。

示例 3:

输入: num = “12345”

输出: 0

解释:

  • num 的所有排列都是不平衡的。所以答案为 0 。

提示:

  • 2 <= num.length <= 80
  • num 中的字符只包含数字 '0''9'

题解

方法一:

思路

设$num$长度为$n$,$num$中数字总和为$s$,$nums$中数字$i$出现的次数为$c_i$

我们需要选取$C = \lfloor \frac{n}{2} \rfloor$个数字使得和为$V=\frac{s}{2}$

如果s是个奇数是没有答案的。

可以转化为背包计数问题,总共有10件物品,每件物品i最少选择0次,最多选择$c_i$次,容量1为$V$,背包容量2为$C$。每件物品i的体积1为i,体积2为1。

定义$f_{i,j,k}$为前i件物品中容量1恰好达到j,容量2恰好达到k的方案数。

对于合法方案的计算,共选择了C个物品,我们需要计算排列数$C!$,但是存在重复的物品,如果我们选择了物品i的$u_i$次,那么根据多重全排列,有$\frac{C!}{\prod u_i!}$种方案,对于剩下的没有选择的物品$\frac{(n-C)!}{\prod (c_i - u_i)!}$种方案,乘法原理得到方案数为$\frac{C!(n-C)!}{\prod u_i!(c_i - u_i)!}$。

$f_{i,j,k} = \sum \limits_{u=0}^{c_i} f_{i-1,j-i\cdot u,k-u}\frac{C!(n-C)!}{ u!(c_i - u)!}, j\ge i\cdot u , k\ge u$

恰好型背包,初始化$f_{0,0,0} = C!(n-C)!$

答案是$f_{10, V, C}$

预处理阶乘和阶乘逆元,阶乘逆元模质数可以基于费马小定理快速幂得到。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
class Solution {
public:
using ll = long long;
const ll mod = 1e9+7;
ll fpow(ll x, ll p) {
ll rt = 1;
while (p) {
if (p&1) rt = rt*x%mod;
x = x*x%mod;
p>>=1;
}
return rt;
}
int countBalancedPermutations(string num) {
int n = num.size();
vector<ll> fac(n+1), inv(n+1);
fac[0] = 1;
for (int i=1; i<=n; i++) fac[i] = fac[i-1]*i%mod;
inv[n] = fpow(fac[n], mod-2);
for (int i=n-1; i>=0; i--) {
inv[i] = inv[i+1]*(i+1)%mod;
}
vector<int> c(10);
int s = 0;
for (char i:num) c[i-'0']++, s += i-'0';
if (s%2) return 0;
int V = s/2, C = n/2;
// solution 1
// ll f[11][V+1][C+1];
// memset(f, -1, sizeof(f));
// function<ll(int,int,int)> dfs = [&](int p, int val, int cnt) {
// if (f[p][val][cnt] != -1) return f[p][val][cnt];
// if (p == 10) {
// return val == 0 && cnt == 0 ? fac[C]*fac[n-C]%mod : 0LL;
// }
// f[p][val][cnt] = 0;
// for (int i=0; i<=c[p]; i++) {
// if (val>=i*p && cnt>=i)
// f[p][val][cnt] += dfs(p+1, val-i*p, cnt-i)*inv[i]%mod*inv[c[p]-i]%mod;
// f[p][val][cnt] %= mod;
// }
// return f[p][val][cnt];
// };
// return dfs(0, V, C)%mod;

// solution 2
// ll f[11][V+1][C+1];
// memset(f, 0, sizeof(f));
// f[0][0][0] = fac[C]*fac[n-C]%mod;
// for (int i=1; i<=10; i++) {
// for (int j=0; j<=V; j++) {
// for (int k=0; k<=C; k++) {
// for (int u=0; u<=c[i-1]; u++) {
// if (j>=u*(i-1) && k>=u) {
// f[i][j][k] += f[i-1][j-u*(i-1)][k-u]*inv[u]%mod*inv[c[i-1]-u]%mod;
// f[i][j][k] %= mod;
// }
// }
// }
// }
// }
// return f[10][V][C];

// solution 3
// ll f[2][V+1][C+1];
// memset(f, 0, sizeof(f));
// f[0][0][0] = fac[C]*fac[n-C]%mod;
// for (int i=1; i<=10; i++) {
// for (int j=0; j<=V; j++) {
// for (int k=0; k<=C; k++) {
// f[i%2][j][k] = 0;
// for (int u=0; u<=c[i-1]; u++) {
// if (j>=u*(i-1) && k>=u) {
// f[i%2][j][k] += f[1-i%2][j-u*(i-1)][k-u]*inv[u]%mod*inv[c[i-1]-u]%mod;
// f[i%2][j][k] %= mod;
// }
// }
// }
// }
// }
// return f[10%2][V][C];

// solution 4
ll f[V+1][C+1];
memset(f, 0, sizeof(f));
f[0][0] = fac[C]*fac[n-C]%mod;
for (int i=1; i<=10; i++) {
for (int j=V; j>=0; j--) {
for (int k=C; k>=0; k--) {
f[j][k] = f[j][k]*inv[c[i-1]]%mod;
for (int u=1; u<=c[i-1]; u++) {
if (j>=u*(i-1) && k>=u) {
f[j][k] += f[j-u*(i-1)][k-u]*inv[u]%mod*inv[c[i-1]-u]%mod;
f[j][k] %= mod;
}
}
}
}
}
return f[V][C];
}
};