LibreOJ 3910 「PA 2022」Mędrcy

rizynvu發表於2024-07-01

考慮找一下走掉的條件:

  1. \(x\)\(1\) 天走掉,那麼就說明 \(x\) 沒有知道任何咒語。
  2. \(x\)\(2\) 天走掉,那麼就說明應該存在一個 \(y\),按照 \(x\) 已知的資訊,\(y\) 應該沒有掌握咒語,但是 \(y\) 第一天沒走。
  3. \(x\)\(3\) 天走掉,那麼就說明應該存在一個 \((y, z)\),按照 \(x\) 已知的資訊應當不存在。
  4. 依次類推,若 \(x\)\(c\) 天走掉,則說明存在 \((a_1, a_2,\cdots, a_{c - 1})\),按照 \(x\) 已知的資訊應當不存在這種共同存在的情況。

轉化一下,用 \(S_x\) 表示 \(x\) 會的咒語的集合。

  1. 若第 \(1\) 天走掉,那麼說明 \(S_x = \varnothing\)
  2. 若第 \(2\) 天走掉,那麼說明存在 \(y\) 使得 \(S_x\cap S_y = \varnothing\),即不存在 \(x, y\) 共存的咒語,\(x\) 就認為 \(y\)\(x\) 就認為 \(y\) 沒有掌握咒語。
  3. 若第 \(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\)
這啟發去試著弄一些優一點的東西。

考慮樸素的求解,即是遍歷到一個點,並去抉擇是否將其加入點集,然後遞迴下去,分為兩種:

  1. 在點集裡。
  2. 不在點集裡,那麼與其有連邊的點都需要選。

考慮每次選剩餘度數最大的點 \(u\)

  • 若最大度數 \(\le 2\),那麼只有環和鏈,可以直接處理。
    對於環,最小覆蓋即為 \(\lceil\frac{len}{2}\rceil\),每個點都有可能被選入。
    對於鏈,分討一下長度 \(len\) 的奇偶:
    1. 為偶,則最小覆蓋為 \(\frac{len}{2}\),每個點都有可能。
    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;
}

相關文章