模板题链接 【模板】2-SAT 问题 - 洛谷
首先,什么是 2-SAT ?
我们考虑一个问题。(题意转化自[JSOI2010] 满汉全席)
你是一位厨师,现在你的餐厅来了m个客人,你有n种材料,每种材料都可以做出两种菜系的美食。每个客人都有一定的需求,他们都点了两道菜品,并且必须吃到一样符合的菜品才会满足。为了满足客人,你需要把n种材料做成不同的美食,但是你嫌麻烦,不想反复做菜,于是做成了一大锅菜,导致每种材料只能做一种菜品。那么有没有一种方案能在能偷懒的条件满足所有的客人呢?
假设第一种菜系为H,第二种菜系为M。
假设你有三种材料,来了四个客人,现在第一个客人点了第一种材做的M系菜品(简称M1)与第二种材料做的M系菜品(简称M2),第二个客人点了M3和H1,第三个客人点了H1和H3,第四个点了H3和M2。
如何偷懒呢?选材料一做H菜系,这样就满足了二三号顾客的需求,再用材料二做M菜系,这样就满足了一四号顾客的需求,这样我们既完成了任务,又成功偷懒了。
以上的问题,就是一个经典的 2-SAT 问题。
2-SAT,即two-satisfiability,通俗来说,就是给出n个集合,每个集合有两个元素,已知若干个,表示a与b矛盾(其中a与b属于不同的集合)。然后从每个集合选择一个元素,判断能否一共选n个两两不矛盾的元素。显然可能有多种选择方案,一般题中只需要求出一种即可。(定义摘自oi-wiki)
那么如何解决这个问题呢?
这里我介绍的是 Tarjan-缩点 算法。
为什么会想到建边跑强联通呢?
我们考虑对于一个条件,如果要满足这个条件,则必须满足另一个条件。
即选 X —> Y,如果我们当成路径来看,走X这条路,必然会走Y这条路,这不就是是一个有向边吗?
如果选了 X 后不能选 Z,那么X这条路径上,一定不能经过Z,如果经过了Z,那么就不符合题意了。所以我们就想到,要判断X与Z是否存在同一个集合中,若存在,在不符合,不存在则符合。
这样的问题,不就是一个强联通分量的问题吗?所以我们缩点,缩完后检测X与Z是否被缩成了一个点,就可以判断是否存在方案了。
关于强联通缩点问题,可以去看【模板】缩点 - 洛谷,缩点的模板。看完之后再回来做这类题。
//一般的tarjan
void tarjan(int x){
s[++top]=x;
vis[x]=true;
dfn[x]=low[x]=++cnt;
for(int i=head[x];i;i=e[i].nex){
int v=e[i].v;
if(!dfn[v]){
tarjan(v);
low[x]=min(low[x],low[v]);
}
else if(vis[v]){
low[x]=min(low[x],dfn[v]);
}
}
if(dfn[x]==low[x]){//一般的缩点
int c;cnts++;
do{
c=s[top--],vis[c]=0;
bel[c]=cnts;
}while(c^x);
}
}
P4782的输入,有点奇怪,这里直接附上代码。
scanf("%d%d%d%d",&x,&x1,&y,&y1);
if(x1&&y1)add(x+n,y),add(y+n,x);
if(!x1&&y1)add(x,y),add(y+n,x+n);
if(x1&&!y1)add(x+n,y+n),add(y,x);
if(!x1&&!y1)add(x,y+n),add(y,x+n);
其实可以去看第一篇题解理解题意。对于不同的 2-SAT 问题,其建图的方式也有所不同,这点可以自己下来刷题体会一下。
P4782完整代码:
#include
#include
#include
using namespace std;
const int N=2e6+10;
struct node{
int v,nex;
}e[N];
int head[N],tote=0;
void add(int u,int v){
e[++tote].v=v,e[tote].nex=head[u],head[u]=tote;
}
int bel[N];
int top=0,cnt=0,cnts=0;
int dfn[N],low[N];
bool vis[N];
int s[N];
void tarjan(int x){
s[++top]=x;
vis[x]=true;
dfn[x]=low[x]=++cnt;
for(int i=head[x];i;i=e[i].nex){
int v=e[i].v;
if(!dfn[v]){
tarjan(v);
low[x]=min(low[x],low[v]);
}
else if(vis[v]){
low[x]=min(low[x],dfn[v]);
}
}
if(dfn[x]==low[x]){
int c;cnts++;
do{
c=s[top--],vis[c]=0;
bel[c]=cnts;
}while(c^x);
}
}
int n,m;
int x,y,x1,y1;
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++){
scanf("%d%d%d%d",&x,&x1,&y,&y1);
if(x1&&y1)add(x+n,y),add(y+n,x);
if(!x1&&y1)add(x,y),add(y+n,x+n);
if(x1&&!y1)add(x+n,y+n),add(y,x);
if(!x1&&!y1)add(x,y+n),add(y,x+n);
}
for(int i=1;i<=2*n;i++)if(!dfn[i])tarjan(i);//记住是2*n!!!!!
for(int i=1;i<=n;i++){
if(bel[i]==bel[i+n]){
puts("IMPOSSIBLE");
return 0;
}
}
puts("POSSIBLE");
for(int i=1;i<=n;i++){
if(bel[i]
例题:
满汉全席
和平委员
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)