🔗 🟡 2741. Special Permutations 2021

tags: Weekly Contest 350 狀態壓縮(Bitmask) 動態規劃(DP) 位運算(Bit Manipulation) 狀壓DP

題意

給定一個包含 nn不同 正整數的整數陣列 numsnums ,下標從 00 開始。如果 numsnums 的一個排列 perm\text{perm} 滿足以下條件,則稱其為特殊排列:

  • 對於 0i<n10 \leq i < n - 1 的下標 ii ,滿足 perm[i]modperm[i+1]=0perm[i+1]modperm[i]=0\text{perm}[i] \bmod \text{perm}[i+1] = 0 \lor \text{perm}[i+1] \bmod \text{perm}[i] = 0

返回特殊排列的總數量,由於答案可能很大,請將其對 109+710^9 + 7 取模後返回。

約束條件

  • 2n142 \leq n \leq 14
  • 1nums[i]1091 \leq \text{nums}[i] \leq 10^9

思路:狀壓 DP :排列型-相鄰相關

dfs(S,i)dfs(S, i) 表示當前已選擇的集合為 SS,且最後一個選擇的數字是 nums[i]nums[i] 時,滿足條件的特殊排列的數量。則我們可以得到狀態轉移方程:

  • dfs(S,i)=j=0n1dfs(S{j},j)dfs(S, i) = \displaystyle\sum_{j=0}^{n-1} dfs(S \cup \{j\}, j) ,其中 jj 是下一個選擇的數字的下標。
    • 需滿足 jSj \notin Snums[i]modnums[j]=0nums[j]modnums[i]=0nums[i] \bmod nums[j] = 0 \lor nums[j] \bmod nums[i] = 0
  • 遞迴終點為 S=US = U ,即所有數字都已經被選擇,其中 UU宇集合(Universal Set)

而注意到 S=n14|S| = n \leq 14 ,因此我們可以使用一個 1414 位的二進制數字來表示 SS ,其中第 ii 位為 11 表示 ii 已經被選擇,為 00 表示未被選擇, 狀態壓縮 成一個整數,並將集合的運算轉換為位運算(Bit Manipulation)。

  • 由於添加到集合的操作會使該位從 00 變為 11 ,因此可以使用 s(1<<j)s | (1 << j) 來表示添加 jjSS 中。
  • 宇集合 UU 可以表示為 u=(1<<n)1u = (1 << n) - 1

由於會存在一些重複的子問題,因此我們可以使用 動態規劃(DP) 來避免重複計算,這裡使用了 Top-Down 的方式進行遞迴計算,並使用 記憶化搜尋(Memoization) 來保存已經計算過的結果。

初始化時可以考慮第一個選擇的數字,即 dfs(1<<i,i)dfs(1 << i, i) ,其中 iinumsnums 的下標,將其所有的結果相加即為答案。

複雜度分析

  • 時間複雜度:O(n22n)\mathcal{O}(n^2 \cdot 2^n),其中 nnnumsnums 的長度。總共有 n2nn \cdot 2^n 種狀態,每種狀態需要 O(n)\mathcal{O}(n) 的時間進行轉移計算。
  • 空間複雜度:O(n2n)\mathcal{O}(n \cdot 2^n),即狀態數量。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Solution:
def specialPerm(self, nums: List[int]) -> int:
MOD = 10 ** 9 + 7
n = len(nums)
u = (1 << n) - 1 # 全部選擇

@cache
def dfs(s: int, i: int) -> int: # s: 已選擇的集合, i: 前一個選擇的 index
if s == u:
return 1
res = 0
pre = nums[i]
for j, x in enumerate(nums): # 枚舉下一個選擇的 index
if (s >> j) & 1: # 已選擇過
continue
if pre % x == 0 or x % pre == 0: # 符合條件
res += dfs(s | (1 << j), j)
return res

return sum(dfs(1 << i, i) % MOD for i in range(n)) % MOD
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
class Solution {
public:
const int MOD = 1e9 + 7;
int specialPerm(vector<int>& nums) {
int n = nums.size();
int u = (1 << n) - 1;

vector<vector<int>> memo(1 << n, vector<int>(n, -1));
function<int(int, int)> dfs = [&](int s, int i) -> int {
if (s == u) return 1;
if (memo[s][i] != -1) return memo[s][i];
int res = 0;
for (int j = 0; j < n; j++) {
if ((s >> j) & 1) continue;
if (nums[i] % nums[j] == 0 || nums[j] % nums[i] == 0) {
res = (res + dfs(s | (1 << j), j)) % MOD;
}
}
return memo[s][i] = res;
};

int ans = 0;
for (int i = 0; i < n; i++) {
ans = (ans + dfs(1 << i, i)) % MOD;
}
return ans;
}
};

類題

狀壓DP:排列型-相鄰無關

狀壓DP:排列型-相鄰相關

題單來自 @灵茶山艾府


寫在最後

Cover photo is generated by @たろたろ, thanks for their work!