考慮找一下走掉的條件:
- 若 \(x\) 第 \(1\) 天走掉,那麼就說明 \(x\) 沒有知道任何咒語。
- 若 \(x\) 第 \(2\) 天走掉,那麼就說明應該存在一個 \(y\),按照 \(x\) 已知的資訊,\(y\) 應該沒有掌握咒語,但是 \(y\) 第一天沒走。
- 若 \(x\) 第 \(3\) 天走掉,那麼就說明應該存在一個 \((y, z)\),按照 \(x\) 已知的資訊應當不存在。
- 依次類推,若 \(x\) 第 \(c\) 天走掉,則說明存在 \((a_1, a_2,\cdots, a_{c - 1})\),按照 \(x\) 已知的資訊應當不存在這種共同存在的情況。
轉化一下,用 \(S_x\) 表示 \(x\) 會的咒語的集合。
- 若第 \(1\) 天走掉,那麼說明 \(S_x = \varnothing\)。
- 若第 \(2\) 天走掉,那麼說明存在 \(y\) 使得 \(S_x\cap S_y = \varnothing\),即不存在 \(x, y\) 共存的咒語,\(x\) 就認為 \(y\),\(x\) 就認為 \(y\) 沒有掌握咒語。
- 若第 \(c\) 天走掉,那麼說明存在 \((a_1, a_2, \cdots, a_{c - 1})\),\(S_x\cap S_{a_1}\cap S_{a_2}\cap \cdots \cap S_{a_{c - 1}} = \varnothing\)。
進一步的,考慮轉化為補集的形式,令 \(T_x\) 表示 \(x\) 不會的咒語的集合。
那麼第 \(c\) 天就走掉,那麼說明存在 \((a_1, a_2, \cdots, a_{c - 1})\),\(T_x\cup T_{a_1}\cup T_{a_2}\cup \cdots \cap T_{a_{c - 1}} = \text{U}\)(\(\text{U}\) 指全集)。
然後因為又有每個咒語只有 \(2\) 個賢者沒有掌握,考慮把咒語當作邊,賢者當作點。
然後能發現要求的即是最小點覆蓋和對應的方案。
這是經典 NPC 問題,但是能發現這裡給了個特殊的 \(k\le 30\)。
這啟發去試著弄一些優一點的東西。
考慮樸素的求解,即是遍歷到一個點,並去抉擇是否將其加入點集,然後遞迴下去,分為兩種:
- 在點集裡。
- 不在點集裡,那麼與其有連邊的點都需要選。
考慮每次選剩餘度數最大的點 \(u\)。
- 若最大度數 \(\le 2\),那麼只有環和鏈,可以直接處理。
對於環,最小覆蓋即為 \(\lceil\frac{len}{2}\rceil\),每個點都有可能被選入。
對於鏈,分討一下長度 \(len\) 的奇偶:- 為偶,則最小覆蓋為 \(\frac{len}{2}\),每個點都有可能。
- 為奇,則最小覆蓋為 \(\lfloor\frac{len}{2}\rfloor\),只有選擇按順序的第 \(2, 4, \cdots\) 的點是最優的。
- 若最大度數 \(> 3\),那麼就用上文提到的樸素遞迴。
考慮複雜度,令 \(T(k)\) 為還能選 \(k\) 個點的複雜度,不難發現第二種情況一種會確定 \(1\) 個點,一種會確定 \(\ge 4\) 個點(相鄰的與自己),所以有 \(T(k) = T(k - 1) + T(k - 4)\),有 \(T(30) = 6272\)。
時間複雜度 \(\mathcal{O}(T(k)(n + m))\)。
注意判斷重邊,重邊會使得度數實際上是假的。
#include<bits/stdc++.h>
const int maxn = 1e3 + 10;
int n, ed;
std::vector<int> to[maxn], P[maxn];
int deg[maxn];
int vis[maxn], ch[maxn], ans[maxn], ud[maxn], mn, now;
void dfsl(int u, int fa, int id) {
if (ud[u]++) return ;
P[id].push_back(u);
for (int v : to[u])
if (! vis[v] && v != fa)
dfsl(v, u, id);
}
inline void del(int u, int val) {
for (int v : to[u])
deg[v] -= val;
}
void dfs() {
if (now > mn) return ;
int u = 0;
for (int i = 1; i <= n; i++)
if (! vis[i] && deg[i] > deg[u])
u = i;
if (! u) {
if (now < mn)
mn = now, memset(ans, 0, sizeof(int) * (n + 1));
for (int i = 1; i <= n; i++)
ans[i] |= ch[i];
} else if (deg[u] > 2) {
vis[u] = 1, del(u, 1);
ch[u] = 1, now++;
dfs();
ch[u] = 0, now--;
std::vector<int> V;
for (int v : to[u])
! vis[v] && (V.push_back(v), del(v, 1), vis[v] = ch[v] = 1, now++);
dfs();
for (int v : V)
vis[v] = ch[v] = 0, del(v, -1), now--;
vis[u] = 0, del(u, -1);
} else {
memset(ud, 0, sizeof(int) * (n + 1));
std::vector<std::pair<int, int> > tp;
for (int i = 1; i <= n; i++)
if (! vis[i] && ! ud[i] && deg[i] <= 1)
P[i].clear(), dfsl(i, 0, i), tp.emplace_back(P[i].size() & 1, i);
for (int i = 1; i <= n ;i++)
if (! vis[i] && ! ud[i])
P[i].clear(), dfsl(i, 0, i), tp.emplace_back(2, i);
for (auto [op, p] : tp)
now += P[p].size() + (op == 2) >> 1;
if (now < mn)
mn = now, memset(ans, 0, sizeof(int) * (n + 1));
if (now == mn) {
for (int i = 1; i <= n; i++)
ans[i] |= ch[i];
for (auto [op, p] : tp)
if (op == 0 || op == 2) {
for (int v : P[p])
ans[v] = 1;
} else {
for (int i = 1; i < P[p].size(); i += 2)
ans[P[p][i]] = 1;
}
}
for (auto [op, p] : tp)
now -= P[p].size() + (op == 2) >> 1;
}
}
inline void Main() {
int m;
scanf("%d%d%d", &n, &m, &ed);
for (int i = 1; i <= n; i++) to[i].clear();
std::unordered_map<int, int> s;
for (int x, y; m--; ) {
scanf("%d%d", &x, &y);
if (x > y) std::swap(x, y);
if (s[x * n + y]++) continue;
to[x].push_back(y), to[y].push_back(x);
}
for (int i = 1; i <= n; i++) deg[i] = to[i].size();
mn = ed + 1, dfs();
if (mn > ed) return puts("-1"), void();
std::vector<int> U;
for (int i = 1; i <= n; i++)
if (ans[i]) U.push_back(i);
printf("%d %zu\n", mn, U.size());
for (int x : U) printf("%d ", x);
puts("");
}
int main() {
int T;
scanf("%d", &T);
while (T--) Main();
return 0;
}