洛谷P2962 [USACO09NOV]Lights G 題解 折半搜尋/列舉
阿新 • • 發佈:2020-09-17
題目連結:https://www.luogu.com.cn/problem/P2962
題目大意:
有 \(n(1 \le n \le 35)\) 盞燈,每盞燈與若干盞燈相連,每盞燈上都有一個開關,如果按下一盞燈上的開關,這盞燈以及與之相連的所有燈的開關狀態都會改變。一開始所有燈都是關著的,你需要將所有燈開啟,求最小的按開關次數。
解題思路:
http://oi-wiki.com/search/bidirectional/
基於這種思想,通過兩次搜尋把前一半和後一半的狀態都枚舉出來然後進行匹配。
但是我這裡是基於狀態壓縮的思想將所有狀態用迴圈枚舉出來的。
示例程式碼:
#include <bits/stdc++.h> using namespace std; const int maxn = 36, maxm = 1200; map<long long, int> mp; struct Edge { int v, nxt; Edge() {}; Edge(int _v, int _nxt) { v = _v; nxt = _nxt; } } edge[maxm]; int n, m, head[maxn], ecnt, ans = INT_MAX; void init() { ecnt = 0; memset(head, -1, sizeof(int)*(n+1)); } void addedge(int u, int v) { edge[ecnt] = Edge(v, head[u]); head[u] = ecnt ++; edge[ecnt] = Edge(u, head[v]); head[v] = ecnt ++; } int main() { scanf("%d%d", &n, &m); init(); while (m --) { int a, b; scanf("%d%d", &a, &b); addedge(a, b); } for (long long s = 0; s < (1<<(n/2)); s ++) { long long st = 0; for (int u = 1; u <= n/2; u ++) { if ((1<<(u-1)) & s) { st ^= (1<<(u-1)); for (int i = head[u]; i != -1; i = edge[i].nxt) { int v = edge[i].v; st ^= (1<<(v-1)); } } } long long st2 = 0; for (int i = 0; i < n; i ++) if (!(st & (1LL<<i))) st2 ^= (1LL<<i); if (mp.find(st2) == mp.end()) mp[st2] = __builtin_popcount(s); else mp[st2] = min(mp[st2], __builtin_popcount(s)); } for (long long s = 0; s < (1<<(n-n/2)); s ++) { long long st = 0; for (int u = n/2+1; u <= n; u ++) { if ((1<<(u-n/2-1)) & s) { st ^= (1LL<<(u-1)); for (int i = head[u]; i != -1; i = edge[i].nxt) { int v = edge[i].v; st ^= (1LL<<(v-1)); } } } if (mp.find(st) != mp.end()) ans = min(ans, mp[st] + __builtin_popcount(s)); } printf("%d\n", ans); return 0; }