Lights G \operatorname{Lights\ G} Lights G
题目链接: luogu P2962 \operatorname{luogu\ P2962} luogu P2962
题目
给出一张 n n n 个点 m m m 条边的无向图,每个点的初始状态都为 0 0 0 。
你可以操作任意一个点,操作结束后所有相邻的端点的状态都会改变,由 0 0 0 变成 1 1 1 或由 1 1 1 变成 0 0 0 。
你需要求出最少的操作次数,使得在所有操作完成之后所有 n n n 个点的状态都是 1 1 1 。
输入
第一行两个整数 n , m n, m n,m 。
之后 m m m 行,每行两个整数 a , b a, b a,b ,表示在点 a , b a, b a,b 之间有一条边。
输出
一行一个整数,表示最少需要的操作次数。
样例输入
5 6
1 2
1 3
4 2
3 4
2 5
5 3
样例输出
3
数据范围
对于 100 % 100\% 100% 的数据, 1 ≤ n ≤ 35 , 1 ≤ m ≤ 595 , 1 ≤ a , b ≤ n 1\le n\le35,1\le m\le595, 1\le a,b\le n 1≤n≤35,1≤m≤595,1≤a,b≤n 。
思路
这道题是一道高斯消元题,不过是异或。
(半模板题吧,就是异或的高斯消元)
显而易见,每个点要么按一次,要么不按。
那方程就是按一个点对其它点有没有影响(或者说按每一个点对它是否有影响),那列出式子就很简单,预处理一下即可。
然后我们就异或的高斯消元。
然后如果没有自由元,我们就可以直接统计出答案。
但是如果有自由元,就要枚举每一个自由元的两种状态,然后不是自由元的部分就可以直接求出来,然后找到需要按的次数最少的一种按法。
然后就是答案了。
代码
#include<cstdio>
#include<algorithm>
using namespace std;
int n, m, x, y, ans, yes[37][37], choi[37];
bool have;
void gaosi() {
//高斯化简异或式
have = 1;
for (int i = 1; i <= n; i++) {
int cho = i;
while (cho <= n && !yes[cho][i]) cho++;
if (cho == n + 1) {
have = 0;
continue;
}
swap(yes[i], yes[cho]);
for (int j = 1; j <= n; j++)
if (i != j && yes[j][i]) {
for (int k = i + 1; k <= n + 1; k++)
yes[j][k] ^= yes[i][k];
yes[j][i] = 0;
}
}
}
void dfs(int now, int num) {
//dfs枚举自由元的每一种方式
if (num >= ans) return ;
if (!now) {
ans = num;
return ;
}
if (yes[now][now]) {
//不是自由元,固定方式
int add = 0;
for (int i = now + 1; i <= n; i++)
if (yes[now][i])
add ^= choi[i];
add ^= yes[now][n + 1];
dfs(now - 1, num + add);
}
else {
//是自由元,两个都dfs一次
dfs(now - 1, num);
choi[now] = 1;
dfs(now - 1, num + 1);
choi[now] = 0;
}
}
int main() {
scanf("%d %d", &n, &m);//读入
for (int i = 1; i <= n; i++) {
//初始化
yes[i][i] = 1;
yes[i][n + 1] = 1;
}
for (int i = 1; i <= m; i++) {
scanf("%d %d", &x, &y);//读入
yes[x][y] = 1;//记录
yes[y][x] = 1;
}
gaosi();//高斯消元
if (have) {
//没有自由元
for (int i = 1; i <= n; i++)
ans += yes[i][n + 1];//直接找到答案
}
else {
//有自由元
ans = 10000;
dfs(n, 0);//dfs枚举
}
printf("%d", ans);//输出
return 0;
}