题意:
有n个房间,初始情况某些房间的门是打开的(会告诉你哪些是打开的)
另外有m个开关。一个开关可能同时连接多个房间的门。且每个房间的门一定被两个开关控制
一旦拨动一个开关,那么这个开关连接的所有门的状态都会转换。
例子:
设1为开,0为关
若初始情况:1 0 1,存在一个开关连接1、2,拨动开关,门的状态会变为0 1 1
问是否有办法同时打开所有的门。
数据范围:n,m<=1e5
解法:
显然一个开关拨动两次相当于没拨动,
因此每个开关最后要么不拨动,要么只拨动一次
拨动和不拨动对应0/1两个状态
强调一下题目条件: “每个房间的门一定被两个开关控制”
因为每个门只被两个开关控制
如果一个门初始为1,那么连接他的两个开关要么全拨动,要么不拨动
如果一个门初始为0,那么连接他的两个开关一定且只能拨动一个
2-sat解法:
n个0/1选择,存在若干个限制条件,问是否有解(有时候需要求出具体解)
在这题的限制条件是:
1.如果门为1,那么两个开关同时拨动或者同时不拨动
2.如果门为0,那么两个开关必须且只能开一个
因此可以是一个2-sat问题,跑tarjan判断是否会发生冲突即可
并查集解法:
因为这题的边是双向的,约等于无向边
因此也可以用并查集直接判断连通性
原理和2-sat一样,只是利用双向边的特殊性
code1:
#include<bits/stdc++.h>
using namespace std;
const int maxm=2e5+5;
int low[maxm],dfn[maxm],idx;
int belong[maxm],sum;
vector<int>gg[maxm];
stack<int>stk;
int ins[maxm];
//
vector<int>g[maxm];
int r[maxm];
int n,m;
void dfs(int x){
low[x]=dfn[x]=++idx;
stk.push(x);
ins[x]=1;
for(int v:gg[x]){
if(!dfn[v]){
dfs(v);
low[x]=min(low[x],low[v]);
}else if(ins[v]){
low[x]=min(low[x],dfn[v]);
}
}
if(low[x]==dfn[x]){
sum++;
while(1){
int t=stk.top();stk.pop();
ins[t]=0;
belong[t]=sum;
if(x==t)break;
}
}
}
//i对应拨动,i+m对应不拨动
signed main(){
cin>>n>>m;
for(int i=1;i<=n;i++)cin>>r[i];
for(int i=1;i<=m;i++){
int k;cin>>k;
while(k--){
int x;cin>>x;
g[x].push_back(i);
}
}
for(int i=1;i<=n;i++){
int a=g[i][0];
int b=g[i][1];
if(r[i]==1){//两个同时开或者同时关
gg[a].push_back(b);
gg[b].push_back(a);
gg[a+m].push_back(b+m);
gg[b+m].push_back(a+m);
}else{//必须且只能开一个
gg[a].push_back(b+m);
gg[b+m].push_back(a);
gg[b].push_back(a+m);
gg[a+m].push_back(b);
}
}
for(int i=1;i<=m*2;i++){
if(!dfn[i]){
dfs(i);
}
}
int ok=1;
for(int i=1;i<=m;i++){
if(belong[i]==belong[i+m]){
ok=0;
break;
}
}
if(ok)puts("YES");
else puts("NO");
return 0;
}
code2:
#include<bits/stdc++.h>
using namespace std;
const int maxm=2e5+5;
vector<int>g[maxm];
int pre[maxm];
int r[maxm];
int n,m;
int ffind(int x){
return pre[x]==x?x:pre[x]=ffind(pre[x]);
}
//i对应拨动,i+m对应不拨动
signed main(){
cin>>n>>m;
for(int i=1;i<=n;i++)cin>>r[i];
for(int i=1;i<=m;i++){
int k;cin>>k;
while(k--){
int x;cin>>x;
g[x].push_back(i);
}
}
for(int i=1;i<=m*2;i++)pre[i]=i;
for(int i=1;i<=n;i++){
int a=g[i][0];
int b=g[i][1];
if(r[i]==1){//两个同时开或者同时关
pre[ffind(a)]=ffind(b);//a和b都开
pre[ffind(a+m)]=ffind(b+m);//a和b都不开
}else{//必须且只能开一个
pre[ffind(a)]=ffind(b+m);//a开b不开
pre[ffind(b)]=ffind(a+m);//b开a不开
}
}
int ok=1;
for(int i=1;i<=m;i++){
if(ffind(i)==ffind(i+m)){//判断是否存在冲突
ok=0;
break;
}
}
if(ok)puts("YES");
else puts("NO");
return 0;
}