您的位置:首页 > 其它

2-sat问题求解(hdu4421)

2012-11-06 11:54 351 查看
本人是小菜一名,但又不甘做小菜。在看到长春赛区现场赛B题(Bit Magic),傻眼了,又不会做。于是网上搜解题思路。发现大家都在说2-sat(本菜以前是没见过),于是乎就又看了大家说的伍昱的《由对称性解2-SAT问题》和赵爽的《2-SAT 解法浅析》。了解了什么是2-sat,什么是2的逻辑判定性,以及2-sat的特性。然后又在poj上做了几题2-sat问题。期间感受就是:大牛们就是牛,他们总结的算法流程也很精辟(膜拜),分享一下:

1.构图 (重点+难点)
2.求图的极大强连通子图 (模板)
3.把每个子图收缩成单个节点,根据原图关系构造一个有向无环图 (模板)
4.判断是否有解,无解则输出(退出) (这块常用到二分枚举答案)
5.对新图进行拓扑排序 (模板)
6.自底向上进行选择、删除 (模板)
7.输出(模板)

然后就自信满满的去做这题Bit Magic(题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=4421)结果又是纠结了很长时间(我那个郁闷啊)。

题目大意:给了一段由一维数组A求二位数组B的代码:



现在反过来,给出B数组,问您能否找出这样的A数组。

根据所给代码,我们可以发现这是典型的2-sat问题(小菜也是在研究2-sat之后才发现的)。思路:依次枚举二进制的每一位:对n个数的每一位x,变成2个点x和~x,其中x表示变量取1,~x表示变量取0。建图方式如下:

1、x&y =1 转化成 (~x-->x)&&(~y-->y)

2、x&y=0 转换成 (x-->~y)&&(y-->~x)

3、x|y=1 转换成 (~x-->y)&&(~y-->x)

4、x|y=0 转换成 (x-->~x)&&(y-->~y)

5、x ^y= 1 转换成 (x-->~y)&&(y-->~x)&&(~x-->y)&&(~y-->x)

6、x ^y= 0 转换成 (x-->y)&&(~y-->~x)&&(~x-->~y)&&(y-->x)

在实际编程中x表示1,x+n表示0。接下来就是套模版了。

#include<iostream>
#include<stack>
using namespace std;
const int maxn=5000;
const int MAX=1000001;
struct node
{
//int from;
int to;
int next;
}edge[MAX];
int cnt,head[maxn];//静态链表头指针
int n;
int b[501][501];
void addedge(int u,int v)
{
// edge[cnt].from=u;//这句如果不注释,你会纠结的发现TLE
edge[cnt].to=v;
edge[cnt].next=head[u];
head[u]=cnt++;
}
void buildgraph(int i,int j,int c)//建图(2-sat问题中的难点也是关键)
{
if(i==j)return;
else if(i%2==1&&j%2==1)
{
if(c)
{
addedge(i+n,j);
addedge(j+n,i);
}
else
{
addedge(i,i+n);
addedge(j,j+n);
}
}
else if(i%2==0&&j%2==0)
{
if(c)
{
addedge(i+n,i);
addedge(j+n,j);
}
else
{
addedge(i,j+n);
addedge(j,i+n);
}
}
else
{
if(c)
{
addedge(i,j+n);
addedge(j,i+n);
addedge(i+n,j);
addedge(j+n,i);
}
else
{
addedge(i,j);
addedge(j,i);
addedge(i+n,j+n);
addedge(j+n,i+n);
}
}
}
stack<int>s;
int dfn[maxn];//记录搜索到该点的时间,也就是第几个搜索这个点的。
int low[maxn];//标记数组,记录该点所在的强连通子图所在搜索子树的根节点的dfn值。
int belong[maxn];//记录每个点属于哪一个强连通分量。
int num,index;
bool instack[maxn];//是否在栈中
void tarjan(int u)//求强连通分量(模版)
{
/*
数组的初始化:当首次搜索到点p时,Dfn与Low数组的值都为到该点的时间。
堆栈:每搜索到一个点,将它压入栈顶。
当点p有与点p’相连时,如果此时(时间为dfn[p]时)p’不在栈中,p的low值为两点的low值中较小的一个。
当点p有与点p’相连时,如果此时(时间为dfn[p]时)p’在栈中,p的low值为p的low值和p’的dfn值中较小的一个。
每当搜索到一个点经过以上操作后(也就是子树已经全部遍历)的low值等于dfn值,则将它以及在它之上的元素弹出栈。这些出栈的元素组成一个强连通分量。
继续搜索(或许会更换搜索的起点,因为整个有向图可能分为两个不连通的部分),直到所有点被遍历。
*/
int i;
dfn[u]=low[u]=++index;
s.push(u);
instack[u]=true;
int v;
for(i=head[u];i!=-1;i=edge[i].next)
{
v=edge[i].to;
if(!dfn[v])
{
tarjan(v);
low[u]=min(low[u],low[v]);
}
else
if(instack[v])low[u]=min(low[u],dfn[v]);
}
if(dfn[u]==low[u])
{
num++;              //强连通分量个数
do
{
v=s.top();
s.pop();
belong[v]=num;   //第v个点属于第num个连通块
instack[v]=false;
}while(u!=v);
}
}
void TwoSat()
{
index=num=0;
memset(low,0,sizeof(low));
memset(dfn,0,sizeof(dfn));
memset(belong,0,sizeof(belong));
memset(instack,false,sizeof(instack));
while(!s.empty())s.pop();
for(int i=0;i<n*2;i++)
if(!dfn[i])tarjan(i);
}
bool judge()//判断是否有解
{
for(int i=0;i<n;i++)
if(belong[i]==belong[i+n])
return 0;
return 1;
}
int main()
{
//freopen("test.txt","r",stdin);
int i,j,c;
while(scanf("%d",&n)!=EOF)
{
for(i=0;i<n;i++)
for(j=0;j<n;j++)
scanf("%d",&b[i][j]);
bool flag=true;
for(i=0;i<n;i++)
{
for(j=0;j<n;j++)
{
if(i==j&&b[i][j]!=0)
{
flag=false;
break;
}
if(b[i][j]!=b[j][i])
{
flag=false;
break;
}
}
}
if(!flag)
{
printf("NO\n");
continue;
}
flag=true;
for(int k=0;k<32;k++)
{
cnt=0;
memset(head,-1,sizeof(head));
for(i=0;i<n;i++)
{
for(j=0;j<n;j++)
{
c=b[i][j]&(1<<k);
buildgraph(i,j,c);
}
}
TwoSat();
if(!judge())
{
flag=false;
break;
}
}
if(flag)printf("YES\n");
else printf("NO\n");
}
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: