• 「LG4782 模板 2-SAT 问题」


    题目

    来学(2)-(sat)

    这个东西确实不难

    这个算法就是给你一堆(bool)变量(x_1,x_2...x_n),之后给你一些限制

    限制的形式就是给你一对((u,o1,v,o2))

    (x_u=o1)或者(x_v=o2)

    之后满足所有限制

    这个东西非常容易就能抽象成一个图论模型

    我们把每个(x_i)拆成(i)(i')两个点,分别表示真和假

    我们对于每一个限制,连出去一些边,边((x,y))的含义是选择了(x)就必须选择(y)

    举个例子吧

    如果一条限制是

    [(u,0,v,1) ]

    那么(u)(v)连边,表示(x_u=1)的时候(x_v)也得等于(1)

    同时(v')(u')连边,表示(x_v=0)的时候(x_u)只能等于(0)

    这样的话如果从(i)能通过这种边一路推到(i')就表示让(x_i=1)的话就必须让(x_i=0)这显然不合理

    于是就能判断无解情况了,一个(tarjan)下去找一个强联通分量

    还有些题目需要我们构造出一组可行解

    其实就是输出(col[i]<col[i'])

    (col[i])就是这个点所属强联通分量的编号

    看起来很神奇,其实是这样的

    我们考虑把(tarjan)缩完点的图建出来,发现我们肯定是尽量优先选择那些拓扑序较大的,因为优先选择拓扑序较大的可以使得必须选择的点少一些

    由于(tarjan)的性质(col)正好是反向拓扑序,于是如果(i)(col)较小,也就是拓扑序较大,那就优先选择(x_i=1)

    没了

    #include<cstdio>
    #include<cstring>
    #include<iostream>
    #define min(a,b) ((a)<(b)?(a):(b))
    #define re register
    #define maxn 2000005
    inline int read() {
    	char c=getchar();int x=0;while(c<'0'||c>'9') c=getchar();
    	while(c>='0'&&c<='9') x=(x<<3)+(x<<1)+c-48,c=getchar();return x;
    }
    struct E{int v,nxt;}e[maxn];
    int n,num,cnt,p,mid,top,m;
    int col[maxn],head[maxn],dfn[maxn],low[maxn],st[maxn],f[maxn];
    inline void add(int x,int y) {e[++num].v=y;e[num].nxt=head[x];head[x]=num;}
    void tarjan(int x) {
    	dfn[x]=low[x]=++cnt;st[++top]=x;f[x]=1;
    	for(re int i=head[x];i;i=e[i].nxt) 
    	if(!dfn[e[i].v]) tarjan(e[i].v),low[x]=min(low[x],low[e[i].v]);
    		else if(f[e[i].v]) low[x]=min(low[x],dfn[e[i].v]);
    	if(low[x]!=dfn[x]) return;++p;
    	do{mid=st[top--];f[mid]=0,col[mid]=p;}while(mid!=x);
    }
    int main() {
    	n=read(),m=read();
    	int x,y,o1,o2;
    	for(re int i=1;i<=m;i++) {
    		x=read(),o1=read(),y=read(),o2=read();
    		if(o1==1) {if(!o2) add(x+n,y+n);else add(x+n,y);}
    		if(o1==0) {if(!o2) add(x,y+n);else add(x,y);}
    		std::swap(x,y),std::swap(o1,o2);
    		if(o1==1) {if(!o2) add(x+n,y+n);else add(x+n,y);}
    		if(o1==0) {if(!o2) add(x,y+n);else add(x,y);}
    	}
    	for(re int i=1;i<=n+n;i++) if(!dfn[i]) tarjan(i);
    	for(re int i=1;i<=n;i++) if(col[i]==col[i+n]) {puts("IMPOSSIBLE");return 0;}
    	puts("POSSIBLE");
    	for(re int i=1;i<=n;i++) printf("%d ",col[i]<col[i+n]);
    	return 0;
    }
    
  • 相关阅读:
    如何获取QQ的clientkey
    自动输入QQ密码
    Ext Tree 操作类
    QQ消息记录文件压缩方法
    见过最恶心的代码,发泄一下。。。
    今天尝试获取QQ的clientkey未果,做个记号
    用c# 调用并调试c++的代码
    托盘管理 隐藏/显示/单击/右键/双击/改变位置
    在XP下是可以查看进程命令行参数的
    充分利用你的“二脑”
  • 原文地址:https://www.cnblogs.com/asuldb/p/10438324.html
Copyright © 2020-2023  润新知