Codeforces Round #400 (Div.1+Div.2) D. The Door Problem【2-SAT】

传送门:CF776 D / NEFU 2041

题意

有n个门,有些是开的,有些是关的,有m个开关控制这些门,一个开关可以控制多个门,但是每个门都被恰好2个开关控制。开关的具体作用是,将所有被控制门的状态取反。是否可以通过开关实现所有门都打开?

思路

本题关键在于“每个门都被恰好2个开关控制”。

那么一个门就是一个限制条件,根据门的初始状态,得到这两个开关到底是选还是不选。如果门初始是开,那么两个开关都选或者都不选;如果门初始是关,那么两个开关中只能选一个。

于是题目就转换为:总共n个限制条件,讨论m个点的取值Ture/False使得满足所有限制条件的2-SAT问题。

AC代码

#include <bits/stdc++.h>
using namespace std;
const int N=2e5+10; // 记得开两倍
int n,m,x[N];
int dfn[N],low[N],tim;
int scc[N],cnt;
vector<int>g[N];
stack<int>s;
vector<int>ans[N];
void dfs(int u)
{
    dfn[u]=low[u]=++tim;
    s.push(u);
    int sz=g[u].size();
    for(int i=0;i<sz;i++)
    {
        int v=g[u][i];
        if(!dfn[v])
        {
            dfs(v);
            low[u]=min(low[u],low[v]);
        }
        else if(!scc[v])
        {
            low[u]=min(low[u],dfn[v]);
        }
    }
    if(low[u]==dfn[u])
    {
        cnt++;
        while(1)
        {
            int v=s.top();s.pop();
            scc[v]=cnt;
            if(v==u)break;
        }
    }
}
void tarjan()
{
    for(int i=1;i<=2*m;i++)
        if(!dfn[i])dfs(i);
}
int f(int i,int w) // 第i个数,取值为w,返回在图中的对应编号
{
    if(w==1)return i;
    return i+m;
}
bool judge()
{
    for(int i=1;i<=m;i++)
        if(scc[i]==scc[i+m])return 0;
    return 1;
}
int main()
{
    ios::sync_with_stdio(false);
    cin>>n>>m;
    for(int i=1;i<=n;i++)
        cin>>x[i];
    int k,door;
    for(int i=1;i<=m;i++)
    {
        cin>>k;
        for(int j=1;j<=k;j++)
        {
            cin>>door;
            ans[door].push_back(i); // 门door被开关i控制
        }
    }
    for(int i=1;i<=n;i++) // 门i
    {
        int p1=ans[i][0]; // 控制门i的第一个开关
        int p2=ans[i][1]; // 控制门i的第二个开关
        int x1=f(p1,1); // p1开
        int nx1=f(p1,0); // p1关
        int x2=f(p2,1); // p2开
        int nx2=f(p2,0); // p2关
        if(x[i]==1) // 两个开关都开,或者都关
        {
            g[x1].push_back(x2); // x1->x2
            g[x2].push_back(x1); // x2->x1
            g[nx1].push_back(nx2); // !x1->!x2
            g[nx2].push_back(nx1); // !x2->!x1
        }
        else // 只能开一个
        {
            g[x1].push_back(nx2); // x1->!x2
            g[nx1].push_back(x2); // !x2->x1
            g[x2].push_back(nx1); // x2->!x1
            g[nx2].push_back(x1); // !x1->x2
        }
    }
    tarjan();
    if(judge())printf("YES\n");
    else printf("NO\n");
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

nefu-ljw

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值