传送门: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;
}