概念
s a t sat sat问题是求满足一些形如 a 1 o r a 2 o r a 3 . . . a_1\ or\ a_2\ or\ a_3... a1 or a2 or a3...的表达式成立的一组解的问题。其中每一个命题 a i a_i ai可以是 x j = 0 x_j=0 xj=0或 x j = 1 x_j=1 xj=1。 s a t sat sat问题通常是 N P NP NP完全问题。 2 − s a t 2-sat 2−sat,顾名思义,就是求满足一些形如 a 1 o r a 2 a_1\ or\ a_2 a1 or a2的表达式成立的一组解的问题。 2 − s a t 2-sat 2−sat是可以在多项式时间内解决的。
方法
可以给每一个 x i x_i xi建立两个点,一个点表示 x i = 0 x_i=0 xi=0,另一个表示 x i = 1 x_i=1 xi=1。现在考虑如何连边。如果按照原表达式的“ o r or or”来连边,最后会出现一些双连通分量,但是不方便我们找到解。但是如果变一下,改成“ a n d and and”来连边,那就会有两条 ¬ a 1 → a 2 \neg a_1\to a_2 ¬a1→a2和 ¬ a 2 → a 1 \neg a_2\to a_1 ¬a2→a1的命题,因为两个条件若有一个没有达成则另一个必须达成。于是,就出现了一个有向图,因为是“ a n d and and”所以每一个强连通分量都必须全选或者全都不选。如果有一个 x i x_i xi选 0 0 0或 1 1 1都在同一个强连通分量中就会导致 x i x_i xi两种值都不选或都选,显然无解。现在考虑如何构造答案。我们假设选的是靠前的强连通分量,如果最后选的是 ¬ a 1 \neg a_1 ¬a1,则必须选 a 2 a_2 a2,而 a 2 a_2 a2在 ¬ a 1 \neg a_1 ¬a1的后面(因为有一条 ¬ a 1 → a 2 \neg a_1\to a_2 ¬a1→a2的边), ¬ a 2 \neg a_2 ¬a2在 a 1 a_1 a1的前面(因为有一条 ¬ a 2 → a 1 \neg a_2\to a_1 ¬a2→a1的边),所以无法保证 a 2 a_2 a2在 ¬ a 2 \neg a_2 ¬a2的前面,与我们假设答案一定在前面的强连通分量矛盾。同理,不难证明答案一定在后面。编号越小的强连通分量越靠后,所以选择编号更小的强连通分量即可。不难证明如果有一个强连通分量,则一定存在另一个强连通分量中的元素全都是与前者相反的,所以上述构造答案的方法保证选出来的答案一定在同一个强连通分量内。
例题
AcWing 2402
板子题,按照上述方法写即可。
#include<bits/stdc++.h>
using namespace std;
const int NN=2e6+4;
int dfn[NN],low[NN],scc[NN],scn,cnt,head[NN],e[NN],ne[NN],idx;
bool in[NN];
stack<int>stk;
void add(int u,int v)
{
e[++idx]=v;
ne[idx]=head[u];
head[u]=idx;
}
void tarjan(int u)
{
low[u]=dfn[u]=++cnt;
in[u]=true;
stk.push(u);
for(int i=head[u];i;i=ne[i])
{
int v=e[i];
if(!dfn[v])
{
tarjan(v);
low[u]=min(low[u],low[v]);
}
else if(in[v])
low[u]=min(low[u],dfn[v]);
}
if(dfn[u]==low[u])
{
int x;
scn++;
do
{
x=stk.top();
stk.pop();
scc[x]=scn;
in[x]=false;
}while(x!=u);
}
}
int main()
{
int n,m;
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
{
int x,a,y,b;
scanf("%d%d%d%d",&x,&a,&y,&b);
add(x*2+!a,y*2+b);
add(y*2+!b,x*2+a);
}
for(int i=2;i<=n*2+1;i++)
if(!dfn[i])
tarjan(i);
for(int i=1;i<=n;i++)
if(scc[i*2]==scc[i*2+1])
{
printf("IMPOSSIBLE");
return 0;
}
puts("POSSIBLE");
for(int i=1;i<=n;i++)
if(scc[i*2]<scc[i*2+1])
printf("0 ");
else
printf("1 ");
return 0;
}
AcWing 371
每一个仪式都是二选一,符合 2 − s a t 2-sat 2−sat。但是每一种选择都是一个区间,并不方便作为条件,所以可以作为点的权值。那么条件就是两个重叠的时段。如果两个时段重叠了,那表达式就是 ¬ a 1 o r ¬ a 2 \neg a_1\ or\neg a_2 ¬a1 or¬a2。
#include<bits/stdc++.h>
using namespace std;
const int NN=2004,MM=4000004;
struct node
{
int s,t,d;
}w[NN];
int dfn[NN],low[NN],scc[NN],scn,cnt,head[NN],e[MM],ne[MM],idx;
bool in[NN];
stack<int>stk;
int is_overlap(int a,int b,int c,int d)
{
return b>c&&d>a;
}
void add(int u,int v)
{
e[++idx]=v;
ne[idx]=head[u];
head[u]=idx;
}
void tarjan(int u)
{
low[u]=dfn[u]=++cnt;
in[u]=true;
stk.push(u);
for(int i=head[u];i;i=ne[i])
{
int v=e[i];
if(!dfn[v])
{
tarjan(v);
low[u]=min(low[u],low[v]);
}
else if(in[v])
low[u]=min(low[u],dfn[v]);
}
if(dfn[u]==low[u])
{
int x;
scn++;
do
{
x=stk.top();
stk.pop();
scc[x]=scn;
in[x]=false;
}while(x!=u);
}
}
int main()
{
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
int h1,s1,h2,s2,d;
scanf("%d:%d %d:%d %d",&h1,&s1,&h2,&s2,&d);
w[i]=(node){h1*60+s1,h2*60+s2,d};
}
for(int i=1;i<=n;i++)
for(int j=i+1;j<=n;j++)
{
node a=w[i],b=w[j];
if(is_overlap(a.s,a.s+a.d,b.s,b.s+b.d))
add(i,j+n),add(j,i+n);
if(is_overlap(a.s,a.s+a.d,b.t-b.d,b.t))
add(i,j),add(j+n,i+n);
if(is_overlap(a.t-a.d,a.t,b.s,b.s+b.d))
add(i+n,j+n),add(j,i);
if(is_overlap(a.t-a.d,a.t,b.t-b.d,b.t))
add(i+n,j),add(j+n,i);
}
for(int i=1;i<=n*2;i++)
if(!dfn[i])
tarjan(i);
for(int i=1;i<=n;i++)
if(scc[i]==scc[i+n])
{
printf("NO");
return 0;
}
puts("YES");
for(int i=1;i<=n;i++)
if(scc[i]<scc[i+n])
printf("%02d:%02d %02d:%02d\n",w[i].s/60,w[i].s%60,(w[i].s+w[i].d)/60,(w[i].s+w[i].d)%60);
else
printf("%02d:%02d %02d:%02d\n",(w[i].t-w[i].d)/60,(w[i].t-w[i].d)%60,w[i].t/60,w[i].t%60);
return 0;
}
AcWing 1032
看数据范围,可以发现 d d d很小,所以可以枚举 x x x填 a , b , c a,b,c a,b,c中的哪一种。但是直接枚举三种可能时间不太够,不难发现只枚举两种可以把剩下的那一种情况枚举到。剩下的就是很裸的 2 − s a t 2-sat 2−sat问题了。每张地图的选法是剩余的两个选一个,当命题,条件就是两个必须都选或者都不选,已经是 a n d and and的形式了,不需要转换成 a 1 → a 2 a_1\to a_2 a1→a2直接连边即可。但是有可能 a 1 a_1 a1选了后 a 2 a_2 a2却一定不能选,所以只连一条都不选的边即可。
#include<bits/stdc++.h>
using namespace std;
const int NN=100004,MM=200004;
struct node
{
int x,y;
char a,b;
}edge[MM];
int n,d,m,xnum[NN],head[NN],ne[MM],e[MM],idx,scn,cnt,low[NN],dfn[NN],in[NN],scc[NN];
char s[NN];
stack<int>stk;
int get(int x,char y,int t)
{
if(((s[x]-'a'+1)%3!=(y-'A'))^t)
return x+n;
else
return x;
}
char put(int x,int t)
{
return 'A'+(s[x]-'a'+t)%3;
}
void add(int u,int v)
{
e[++idx]=v;
ne[idx]=head[u];
head[u]=idx;
}
void tarjan(int u)
{
low[u]=dfn[u]=++cnt;
in[u]=true;
stk.push(u);
for(int i=head[u];~i;i=ne[i])
{
int v=e[i];
if(!dfn[v])
{
tarjan(v);
low[u]=min(low[u],low[v]);
}
else if(in[v])
low[u]=min(low[u],dfn[v]);
}
if(dfn[u]==low[u])
{
int x;
scn++;
do
{
x=stk.top();
stk.pop();
scc[x]=scn;
in[x]=false;
}while(x!=u);
}
}
int main()
{
scanf("%d%d%s%d",&n,&d,s,&m);
for(int i=0,j=0;i<n;i++)
if(s[i]=='x')
xnum[j++]=i;
for(int i=0;i<m;i++)
scanf("%d %c %d %c",&edge[i].x,&edge[i].a,&edge[i].y,&edge[i].b);
for(int i=0;i<1<<d;i++)
{
for(int j=0;j<d;j++)
if(i>>j&1)
s[xnum[j]]='a';
else
s[xnum[j]]='b';
memset(head,-1,sizeof(head));
memset(dfn,0,sizeof(dfn));
idx=-1;
scn=cnt=0;
for(int j=0;j<m;j++)
{
int x=edge[j].x-1,y=edge[j].y-1;
char a=edge[j].a,b=edge[j].b;
if(s[x]!=a+32)
{
if(s[y]!=b+32)
add(get(x,a,0),get(y,b,0)),add(get(y,b,1),get(x,a,1));
else
add(get(x,a,0),get(x,a,1));
}
}
for(int j=0;j<n*2;j++)
if(!dfn[j])
tarjan(j);
bool ok=false;
for(int j=0;j<n;j++)
if(scc[j]==scc[j+n])
{
ok=true;
break;
}
if(ok)
continue;
for(int j=0;j<n;j++)
if(scc[j]<scc[j+n])
printf("%c",put(j,1));
else
printf("%c",put(j,2));
return 0;
}
printf("-1");
return 0;
}