需要点亮的技能:
tarjan
2-SAT问题简述
看这样一个例子:
有一天,万恶的
c
z
h
czh
czh出了一道孙题,是XX树套XX树套XX树。
(以下为了方便,简称A树、B树、C树)
(由于
z
y
d
zyd
zyd很菜,所以这些树只能是线段树或平衡树)
有以下要求:
蒟蒻
z
y
d
zyd
zyd :A树是线段树 或 B树是平衡树。
巨神
m
h
y
mhy
mhy:A树是平衡树 或 C树是线段树。
巨神
y
p
y
ypy
ypy :A树是平衡树 或 C树是平衡树。
c
z
h
czh
czh冥思苦想良久,终于想出了一种解决方法:A树是平衡树,B树是平衡树,C树是线段树。
但由于
c
z
h
czh
czh过于毒瘤,他想要出一道树套树套树套……套树(
n
n
n层嵌套),
m
m
m个人提出了要求。
(
n
,
m
<
=
1
0
6
)
(n,m<=10^6)
(n,m<=106)
现在
c
z
h
czh
czh要满足所有人的要求,但czh太余蠢,请你帮忙想出一种解决方案。
2 − s a t 2-sat 2−sat问题标准题意为:传送门
怎么做呢?
如果每个人的要求超过2个,那就是一个NP完全问题,只能爆搜。
然而发现每个人的要求只有2个,显然可以转化成连边(zyd:怎么显然了qwq)。
回到例子:
把第
i
i
i棵树是线段树转化为
x
i
=
1
x_i=1
xi=1,是平衡树转化为
x
i
=
0
x_i=0
xi=0。
蒟蒻
z
y
d
zyd
zyd的要求是
x
1
=
1
x_1=1
x1=1或
x
2
=
0
x_2=0
x2=0。
也就是说:
若
x
1
!
=
1
x_1!=1
x1!=1,那么一定满足
x
2
=
0
x_2=0
x2=0。
即
x
1
=
0
x_1=0
x1=0若为真,则
x
2
=
0
x_2=0
x2=0为真。 ————(1)
若
x
2
!
=
0
x_2!=0
x2!=0,那么一定满足
x
1
=
1
x_1=1
x1=1。
即
x
2
=
1
x_2=1
x2=1若为真,则
x
1
=
1
x_1=1
x1=1为真。 ————(2)
考虑如何把“若A为真,则B为真”的关系转化为边:
对于每个
i
i
i(
1
≤
i
≤
n
1 \leq i \leq n
1≤i≤n),将
x
i
=
0
x_i=0
xi=0看作一个点,编号为
i
i
i,
x
i
=
1
x_i=1
xi=1看作一个点,编号为
i
+
n
i+n
i+n。
然后对于(1),从
1
1
1向
2
2
2连边;对于(2),从
n
+
2
n+2
n+2到
n
+
1
n+1
n+1连边。
每个要求都连边一次,就把所有要求转化成一个图。
然后会发现,如果点
a
a
a和点
b
b
b在同一个强连通分量里面,那么
a
a
a和
b
b
b的真假是相同的。
证明:
a
a
a和
b
b
b在同一个强连通分量中,说明
a
a
a可以有一条路径通向
b
b
b。
若
a
a
a为真,则
a
a
a到
b
b
b路径上的所有点都为真,
b
b
b也为真。
若
a
a
a为假,同理
b
b
b也为假。
所以同一个强连通分量里所有点的真假性都相同。
因此,如果 i i i和 i + n i+n i+n在同一个强连通分量里,说明 x i = 0 x_i=0 xi=0和 x i = 1 x_i=1 xi=1的真假性相同,显然是不成立的,因此这时不存在解。反之则存在解。
如果存在解,考虑怎样找到一组解:
发现拓扑序靠后的一定是有拓扑序靠前的推出来的。
于是让拓扑序靠后的点为真即可。
毒瘤代码
题目链接:传送门
#include<stdio.h>
#include<cstring>
#include<algorithm>
#include<math.h>
#include<vector>
#define re register int
#define rl register ll
using namespace std;
typedef long long ll;
int read() {
re x=0,f=1;
char ch=getchar();
while(ch<'0' || ch>'9') {
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0' && ch<='9') {
x=10*x+ch-'0';
ch=getchar();
}
return x*f;
}
namespace I_Love {
const int Size=2000005;
int n,m,cnt,head[Size];
struct Edge {
int v,next;
} w[Size<<1];
void AddEdge(int u,int v) {
w[++cnt].v=v;
w[cnt].next=head[u];
head[u]=cnt;
}
int tim,top,tot,stk[Size],dfn[Size],low[Size],belong[Size];
bool vis[Size],val[Size];
void tarjan(int x) {
dfn[x]=low[x]=++tim;
stk[++top]=x;
vis[x]=true;
for(int i=head[x]; i; i=w[i].next) {
int nxt=w[i].v;
if(!dfn[nxt]) {
tarjan(nxt);
low[x]=min(low[x],low[nxt]);
} else if(vis[nxt]) {
low[x]=min(low[x],dfn[nxt]);
}
}
if(dfn[x]==low[x]) {
int y;
tot++;
while(y=stk[top--]) {
belong[y]=tot;
vis[y]=false;
if(x==y) return;
}
}
}
void Fujibayashi_Ryou() {
freopen("testdata.in","r",stdin);
n=read();
m=read();
for(re i=1; i<=m; i++) {
int u=read();
bool a=read();
int v=read();
bool b=read();
//若(u==a)==false 则编号为u+n
AddEdge(u+(!a)*n,v+b*n); //若u!=a 则 v=b
//若(v==a)==false 则编号为v+n
AddEdge(v+(!b)*n,u+a*n); //若v!=b 则 u=a
}
for(re i=1; i<=(n<<1); i++) {
if(!dfn[i]) {
tarjan(i);
}
}
for(re i=1; i<=n; i++) {
if(belong[i]==belong[i+n]) {
puts("IMPOSSIBLE");
return;
}
}
puts("POSSIBLE");
for(re i=1; i<=n; i++) {
printf("%d ",belong[i]>belong[i+n]);
}
}
}
int main() {
I_Love::Fujibayashi_Ryou();
return 0;
}