CodeForces776 D. The Door Problem (2-SAT/并查集)

题意:

有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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值