【题解】$luoguP2962$ - 双向广搜 - 异或高斯消元
luoguP2962
题目描述
节日宴会上,我们有 \(N\) 盏彩色灯,他们分别从 \(1\) 到 \(N\) 被标上号码。有 \(M\) 条边连接着这些灯,当按下某一盏灯的开关的时候,这盏灯本身以及所有和这盏灯有边相连的灯的开关状态都会发生改变。
最开始所有灯都是被关着的,问需要至少按下多少开关,才可以把所有灯打开。
\(N ≤ 36\)
(你谷的翻译好像有点问题,就搬的另一个翻译,按照这个写就是对的)
\(\\\)
\(\\\)
\(Solution\)
本题有两种解法
\(1.\) 双向广搜
观察 \(n\) 特别小,但是 \(O(2^n * n)\) 又跑不过,于是想到双向广搜,复杂度为 \(O(2^{\frac{n}{2}} * 2 * n)\)
先搜前 \(\frac{n}{2}\) 个,记录搜到的状态的最少开关数,再搜后 \(\frac{n}{2}\) 个,看是否有对应的解
要注意的是,1<<n
的范围只能是 \(int\),所以要预处理一个数组表示
\(\\\)
\(2.\) (大概是?)正解
首先设最后答案中,第 \(i\) 的状态为 \(x_i\),\(x_i \ = \ 0\) 则不按这盏灯的开关,\(x_i \ = \ 1\) 则为按
那么对于任意点 \(i\) 可以得到一个异或方程, \(x_i \^{} \ x_{i1} \^{} \ x_{i2} \^{} \ ... \ x_{ix}\) ,其中 \(x_{i1} ~ x_{ix}\) 为 \(i\) 连到的灯
总共有 \(n\) 个方程,然后可以用高斯消元解这些方程
异或方程和加减法的差不多,只用把里面的运算规则按照异或的写就好了。推荐先手写几个式子解一下。
但是可能有一些无解,怎么办?
再看看最开始设状态,发现每个 \(x_i\) 只有两个值,所以直接丢进深搜里, \(x \ = \ 0\) 和 \(x \ = \ 1\) 都搜一遍
照理来说复杂度大概还是 \(O(2^n * n)\) ,但是很多都是有解的了,再加最优性剪枝,基本稳过(本来搜索这个东西嘛...复杂度从来都是玄学233
\(\\\)
\(\\\)
Code
双向广搜
#include<bits/stdc++.h>
#define ll long long
#define F(i, x, y) for(int i = x; i <= y; ++ i)
using namespace std;
int read();
const int N = 40;
int n, m, u, v, flag, cnt, ans = INT_MAX;
ll frac[N], p[N], maxn;
map<ll, int> mp;
void dfs(int x, ll now, int tot)
{
if(x == cnt + 1)
{
if(now == maxn) ans = min(ans, tot);
if(! flag)
{
int tmp = mp[now];
if(! tmp || tmp > tot) mp[now] = tot;
}
else
{
int tmp = mp[maxn - now];
if(! tmp) return;
ans = min(ans, tot + tmp);
}
return ;
}
dfs(x + 1, now, tot), dfs(x + 1, now ^ p[x], tot + 1);
}
int main()
{
n = read(), m = read();
frac[1] = 1; F(i, 2, n + 1) frac[i] = frac[i - 1] << 1;
maxn = frac[n + 1] - 1;
F(i, 1, m)
{
u = read(), v = read();
p[u] += frac[v], p[v] += frac[u];//直接预处理好了开关 u 会改变的范围
}
F(i, 1, n) p[i] += frac[i];//一定会改变自己
flag = 0, cnt = n / 2, dfs(1, 0, 0);
flag = 1, cnt = n, dfs(n / 2 + 1, 0, 0);
printf("%d", ans);
return 0;
}
int read()
{
int x = 0, f = 1;
char c = getchar();
while(c < '0' || c > '9') {if(c == '-') f = -1; c = getchar();}
while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
return x * f;
}
\(\\\)
异或高斯消元
#include<bits/stdc++.h>
#define ll long long
#define F(i, x, y) for(int i = x; i <= y; ++ i)
using namespace std;
int read();
const int N = 40;
int n, m, u, v, ans = INT_MAX;
int a[N][N], b[N];
void Gauss()//高斯消元
{
F(i, 1, n)
{
int now = i;
F(j, i, n) if(a[j][i] > a[now][i]) now = j;
if(now != i) F(j, 1, n + 1) swap(a[now][j], a[i][j]);
if(! a[i][i]) continue;
F(j, 1, n)
if(j != i && a[j][i])
F(k, 1, n + 1) a[j][k] ^= a[i][k];
}
}
void dfs(int now, int tot)
{
if(tot > ans) return;
if(! now) return void(ans = tot);
if(a[now][now])//如果有解
{
b[now] = a[now][n + 1];
F(j, now + 1, n) b[now] ^= (b[j] & a[now][j]);
/*为什么只用后一段的?万一 now 还和前面的一些点连
边了呢?事实上在高斯消元中那些前面的点被消掉了,
也就是它们总的异或和是0,对 b[now] 没有影响*/
if(b[now]) dfs(now - 1, tot + 1);
else dfs(now - 1, tot);
}
else//如果没有确定解就分两种情况搜
{
b[now] = 0, dfs(now - 1, tot);
b[now] = 1, dfs(now - 1, tot + 1);
}
}
int main()
{
n = read(), m = read();
F(i, 1, m) u = read(), v = read(), a[u][v] = a[v][u] = 1;
F(i, 1, n) a[i][i] = a[i][n + 1] = 1;
Gauss(), dfs(n, 0), printf("%d", ans);
return 0;
}
int read()
{
int x = 0, f = 1;
char c = getchar();
while(c < '0' || c > '9') {if(c == '-') f = -1; c = getchar();}
while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
return x * f;
}