Shuffling Songs§
n的范围很小,我们考虑转化为状压dp。
前期的准备工作就是对字符串进行映射。
考虑设计dp状态,dp[mask][lst],mask表示当前我们的点集,lst表示下一个进入的点会加在lst点后,dp[mask][lst]的值表示当前状态是否合法。
初始化时将所有点集只有一个点的状态设为true,显然只有一项时肯定合法。
考虑状态转移,在dp[mask][lst]合法的前提下,我们枚举之后可能会加入的点(保证其不在我们已有的点集内),进行以下转移。
if (a[i] == a[lst] || b[i] == b[lst]) dp[mask | (1 << i)][i] |= dp[mask][lst];遍历所有合法情况统计当前点集数量,得到最大值,最后需要删除的点就是除去点集合外剩下的部分。
#include <bits/stdc++.h> #define all(a) a.begin(), a.end() #define int long long #define PII pair<int, int> using ll = long long; const int N = 1e5 + 10, INF = 0x3f3f3f3fll, MOD = 998244353ll; using namespace std; void solve() { int n, cnt = 0, ans = 0; cin >> n; map<string, int> id; vector<int> a(n), b(n); for (int i = 0; i < n; i++) { string s1, s2; cin >> s1 >> s2; if (!id.count(s1)) { id[s1] = cnt++; } if (!id.count(s2)) { id[s2] = cnt++; } a[i] = id[s1], b[i] = id[s2]; } vector<vector<int>> dp((1 << n), vector<int>(n, 0)); for (int i = 0; i < n; i++) dp[1 << i][i] = 1; for (int mask = 0; mask < (1 << n); mask++) for (int lst = 0; lst < n; lst++) { if (!dp[mask][lst]) continue; for (int i = 0; i < n; i++) { if (mask >> i & 1) continue; if (a[i] == a[lst] || b[i] == b[lst]) dp[mask | (1 << i)][i] |= dp[mask][lst]; } } for (int mask = 0; mask < (1 << n); mask++) for (int lst = 0; lst < n; lst++) if (dp[mask][lst]) ans = max(ans, (int)__builtin_popcount(mask)); cout << n - ans << '\n'; } signed main() { ios::sync_with_stdio(0), cin.tie(0); int T = 1; cin >> T, cin.get(); while (T--) solve(); return 0; }