P2962 [USACO09NOV]Lights G
https://www.luogu.com.cn/problem/P2962
题目描述
给出一张 n 个点 m 条边的无向图,每个点的初始状态都为 0。
你可以操作任意一个点,操作结束后所有相邻的端点的状态都会改变,由 0 变成 1 或由 1 变成 0。
你需要求出最少的操作次数,使得在所有操作完成之后所有 n个点的状态都是 1。
输入格式
第一行两个整数 n, m。
之后 m行,每行两个整数 a, b,表示在点 a, b 之间有一条边。
输出格式
一行一个整数,表示最少需要的操作次数。
输入输出样例
输入 #1
5 6
1 2
1 3
4 2
3 4
2 5
5 3
输出 #1
3
说明/提示
对于 100\% 的数据,1\le n\le35,1\le m\le595, 1\le a,b\le n1≤n≤35,1≤m≤595,1≤a,b≤n。
解:对每个灯来说,它的状态是由相邻的灯是否按下,和自己是否按下决定的。可以发现灯按两次是相当于没按,所以灯亮灭只于决定它的其他灯按下次数的奇偶决定,所以就可以用异或计算了。每个灯是否按下的状态记为x,xi=1表示第i个灯按下,xi=0表示没按下。
这样就可以对每一个灯列一个方程。比如灯1与灯2,4相邻,则关于灯1状态的方程为1*x1 ^ 1*x2 ^ 0*x3 ^ 1*x4 =1(最终状态要为1)。
列出n个方程后,进行高斯消元,如果方程含有自由元,则枚举每个自由元的状态。计算答案即可。
#include<cstdio> // #include<iostream> // #include<deque> // #include<cstring> // #include<cmath> // #include<map> // #include<vector> // #include<stack> // #include<algorithm> // #include<queue> // #include<set> #include<bits/stdc++.h> #define sd(x) scanf("%d",&x) #define lsd(x) scanf("%lld",&x) #define sf(x) scanf("%lf",&x) #define ms(x,y) memset(x,y,sizeof x) #define fu(i,a,b) for(register int i=a;i<=b;i++) #define fd(i,a,b) for(register int i=a;i>=b;i--) #define all(a) a.begin(),a.end() #define range(a,x,y) a+x,a+y+x #define dbug printf("bbbk\n") #define R register using namespace std; using namespace __gnu_cxx; typedef long long ll; typedef unsigned long long ull; typedef long double ld; typedef pair<ll,ll> P; const int N=1e3+99; const int MN=1e7+9; const ll mod=1e9+7; const int MAX=1e9; const ll INF=1e9+7; int n,m,ans; int a[N][N],b[N][N]; bitset<N> p[N]; int sta[N]; inline bool guass() { bool frre=0;//是否含自由元 for(int now=1;now<=n;now++)//当前主元 { int t=now+1; while(t<=n&&!p[t][now]) t++; if(t>n) {frre=1;continue;}; swap(p[t],p[now]); fu(i,1,n) { if(i==now) continue; if(p[i][now]) p[i]^=p[now]; } }//判断k是不是自由元只要看p[k][k]==1? return frre; } inline void dfs(int x,int num) { if(num>=ans) return;//剪枝,ans取最小值 if(x==0) {ans=min(ans,num);return;} if(p[x][x])//x不是自由元 { int st=p[x][0]; //每个非自由元都只受后面的自由元影响 fu(i,x+1,n) if(p[x][i]) st^=sta[i]; sta[x]=st; dfs(x-1,num+st); } else//有自由元,枚举自由元状态 { dfs(x-1,num); sta[x]=1; dfs(x-1,num+1); sta[x]=0; } } int main() { //freopen("P2962_12.in","r",stdin); cin>>n>>m; fu(i,1,n) p[i][0]=1,p[i][i]=1;//第0列作为增广列 fu(i,1,m) { int u,v;cin>>u>>v; p[u][v]=1;p[v][u]=1; } if(!guass())//有唯一解 { fu(i,1,n) if(p[i][0]) ans++; } else//有自由元,枚举自由元状态 { ans=INF; dfs(n,0); } cout<<ans<<endl; return 0; }
题目描述
给出一张 n 个点 m 条边的无向图,每个点的初始状态都为 0。
你可以操作任意一个点,操作结束后所有相邻的端点的状态都会改变,由 0 变成 1 或由 1 变成 0。
你需要求出最少的操作次数,使得在所有操作完成之后所有 n 个点的状态都是 1。
输入格式
第一行两个整数 n,m。
之后 m 行,每行两个整数 a,b,表示在点 a,b 之间有一条边。
输出格式
一行一个整数,表示最少需要的操作次数。
输入输出样例
说明/提示
对于 100% 的数据,1≤n≤35,1≤m≤595,1≤a,b≤n。