专题训练之2-sat
推荐几篇博客:https://blog.csdn.net/JarjingX/article/details/8521690 研究总结2-sat问题
https://blog.csdn.net/whereisherofrom/article/details/79417926 有向图强连通+2-sat问题
推荐几篇论文:https://wenku.baidu.com/view/afd6c436a32d7375a41780f2.html 由对称性问题解2-sat
https://wenku.baidu.com/view/0f96c3daa58da0116c1749bc.html 2-sat问题解法浅析
tarjan模板
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=;
const int maxm=;
struct edge{
int to,nxt;
}edge[maxm];
int head[maxn],tot;
int low[maxn],dfn[maxn],stack[maxn],belong[maxn];
int index,top;
int scc;
bool vis[maxn];
int num[maxn]; void addedge(int u,int v)
{
edge[tot].to=v;
edge[tot].nxt=head[u];
head[u]=tot++;
} void tarjan(int u)
{
int v;
low[u]=dfn[u]=++index;
stack[top++]=u;
vis[u]=true;
for ( int i=head[u];i!=-;i=edge[i].nxt ) {
v=edge[i].to;
if ( !dfn[v] ) {
tarjan(v);
low[u]=min(low[u],low[v]);
}
else if ( vis[v] ) low[u]=min(low[u],dfn[v]);
}
if ( low[u]==dfn[u] ) {
scc++;
do {
v=stack[--top];
vis[v]=false;
belong[v]=scc;
num[scc]++;
}
while ( v!=u );
}
} void solve(int N)
{
memset(dfn,,sizeof(dfn));
memset(vis,false,sizeof(vis));
memset(num,,sizeof(num));
index=scc=top=;
for ( int i=;i<=N;i++ ) {
if ( !dfn[i] ) tarjan(i);
}
} void init()
{
tot=;
memset(head,-,sizeof(head));
}
tarjan
DFS模板(按字典序大小排列)
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=;
const int maxm=;
struct edge{
int to,nxt;
}edge[maxm];
int head[maxn],tot;
bool vis[maxn];
int s[maxn],top,n; void init()
{
tot=;
memset(head,-,sizeof(head));
} void addedge(int u,int v)
{
edge[tot].to=v;
edge[tot].nxt=head[u];
head[u]=tot++;
} bool dfs(int u)
{
if ( vis[u^] ) return false;
if ( vis[u] ) return true;
vis[u]=true;
s[top++]=u;
for ( int i=head[u];i!=-;i=edge[i].nxt ) {
if ( !dfs(edge[i].to) ) return false;
}
return true;
} bool tsat()
{
memset(vis,false,sizeof(vis));
for ( int i=;i<*n;i+= ) {
if ( vis[i] || vis[i^] ) continue;
top=;
if ( !dfs(i) ) {
while ( top ) vis[s[--top]]=false;
if ( !dfs(i^) ) return false;
}
}
return true;
}
DFS模板
练习题:
1.(HDOJ1814)http://acm.hdu.edu.cn/showproblem.php?pid=1814
题意:有n个政党,每个政党有两个人,先要组织一个委员会需要从每个政党中选出一人。现有m条关系,代表两个代表的敌对关系,当两个代表有敌对关系时不能同时选择这两个代表
分析:2-sat模板题,需要按照字符串从小到大输出,故采用DFS的做法。对于(a,b)存在敌对的关系,即意味着a和b不能同时被选择,连边a->b'和b->a',进行dfs。注意模板中的标号是从[0,2n)的
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=;
const int maxm=;
struct edge{
int to,nxt;
}edge[maxm];
int head[maxn],tot;
bool vis[maxn];
int s[maxn],top,n; void init()
{
tot=;
memset(head,-,sizeof(head));
} void addedge(int u,int v)
{
edge[tot].to=v;
edge[tot].nxt=head[u];
head[u]=tot++;
} bool dfs(int u)
{
if ( vis[u^] ) return false;
if ( vis[u] ) return true;
vis[u]=true;
s[top++]=u;
for ( int i=head[u];i!=-;i=edge[i].nxt ) {
if ( !dfs(edge[i].to) ) return false;
}
return true;
} bool tsat()
{
memset(vis,false,sizeof(vis));
for ( int i=;i<*n;i+= ) {
if ( vis[i] || vis[i^] ) continue;
top=;
if ( !dfs(i) ) {
while ( top ) vis[s[--top]]=false;
if ( !dfs(i^) ) return false;
}
}
return true;
} int main()
{
int m,i,j,k,x,y,z;
bool flag;
while ( scanf("%d%d",&n,&m)!=EOF ) {
init();
while ( m-- ) {
scanf("%d%d",&x,&y);
x--;y--;
addedge(x,y^);
addedge(y,x^);
}
flag=tsat();
if ( flag ) {
for ( i=;i<*n;i++ ) {
if ( vis[i] ) printf("%d\n",i+);
}
}
else printf("NIE\n");
}
return ;
}
HDOJ1814
2.(HDOJ1816)http://acm.hdu.edu.cn/showproblem.php?pid=1816
题意:有n对钥匙和m扇门,每扇门有2个锁,只要能够打开其中一个锁就算能打开门,只有打开前一扇门才有机会去打开下一扇门。同时每对钥匙只能选取其中一把,而只有当锁和钥匙编号相同时才能开锁。现求最多能开几扇门
分析:对于每对钥匙(a,b),因为最多只能选一把所以有a->'b&&b->'a。而对于每扇门的两个锁(x,y)至少需要打开其中一个,有'x->y&&'y->x。需要二分遍历能够开的门的数量mid,只要前mid扇门能够满足条件即可(即对于门从1到mid添边即可),判断的条件没有一个点和它的否定在一个强连通分量中即算满足条件。
注意:二分右边界的初始值需要为m+1(+1的愿意是保证可以取到右边界,又不会超过边界)。总共有2*n把钥匙,有4*n个点,对于点i来说,点i+2*n为其的否定。
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<map>
using namespace std;
const int maxn=;
const int maxm=;
struct edge{
int to,nxt;
}edge[maxm];
struct Edge{
int x,y;
}door[maxm],key[maxm];
int head[maxn],tot;
int low[maxn],dfn[maxn],stack[maxn],belong[maxn];
int index,top;
int scc,n;
bool vis[maxn];
int num[maxn]; void addedge(int u,int v)
{
edge[tot].to=v;
edge[tot].nxt=head[u];
head[u]=tot++;
} void tarjan(int u)
{
int v;
low[u]=dfn[u]=++index;
stack[top++]=u;
vis[u]=true;
for ( int i=head[u];i!=-;i=edge[i].nxt ) {
v=edge[i].to;
if ( !dfn[v] ) {
tarjan(v);
low[u]=min(low[u],low[v]);
}
else if ( vis[v] ) low[u]=min(low[u],dfn[v]);
}
if ( low[u]==dfn[u] ) {
scc++;
do {
v=stack[--top];
vis[v]=false;
belong[v]=scc;
num[scc]++;
}
while ( v!=u );
}
} void solve(int N)
{
memset(dfn,,sizeof(dfn));
memset(vis,false,sizeof(vis));
memset(num,,sizeof(num));
index=scc=top=;
for ( int i=;i<N;i++ ) {
if ( !dfn[i] ) tarjan(i);
}
} void init()
{
tot=;
memset(head,-,sizeof(head));
} bool judge(int mid)
{
init();
for ( int i=;i<=n;i++ ) {
int u=key[i].x;
int v=key[i].y;
addedge(u,v+*n);
addedge(v,u+*n);
}
for ( int i=;i<=mid;i++ ) {
int u=door[i].x;
int v=door[i].y;
addedge(u+*n,v);
addedge(v+*n,u);
}
solve(*n);
for ( int i=;i<=mid;i++ ) {
if ( belong[i]==belong[i+*n] ) return false;
}
return true;
} int main()
{
int m,i,j,k,x,y,z,ans,l,r,mid;
while ( scanf("%d%d",&n,&m)!=EOF && (n+m) ) {
for ( i=;i<=n;i++ ) {
scanf("%d%d",&key[i].x,&key[i].y);
}
for ( i=;i<=m;i++ ) {
scanf("%d%d",&door[i].x,&door[i].y);
}
l=;
r=m+;
while ( r-l> ) {
mid=(l+r)/;
if ( judge(mid) ) l=mid;
else r=mid;
}
printf("%d\n",l);
}
return ;
}
HDOJ1816
3.(POJ3207)http://poj.org/problem?id=3207
题意:一个圆上有n(n <= 1000)个结点,分别按顺序编号0,1,2…n-1,从圆上任意找两个点连接起来,可以选择圆内连,也可以选择圆外连,每个结点只允许连一次。给定m对连接,问是否存在这样一种情况,所得所有连接之间互不相交。
分析:将每条线(每个连接)看作一个状态,一个状态分为圆内连和圆外连两种情况,即将其拆成2个点x,x'。当某两条直接用同一种方式(即都圆内连,或者都圆外连)连接会产生相交时,此时必有一条直接圆内连,另一条直接圆外连,所以建边x-y' y-x' x'-y y-x'。
注意:边的数量可能会非常大,注意存边的数组尽量开大,不然会RE
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=;
const int maxm=;
struct edge{
int to,nxt;
}edge[maxm];
struct node{
int l,r;
}arr[maxm];
int head[maxn],tot;
int low[maxn],dfn[maxn],stack[maxn],belong[maxn];
int index,top;
int scc;
bool vis[maxn];
int num[maxn]; void addedge(int u,int v)
{
edge[tot].to=v;
edge[tot].nxt=head[u];
head[u]=tot++;
} void tarjan(int u)
{
int v;
low[u]=dfn[u]=++index;
stack[top++]=u;
vis[u]=true;
for ( int i=head[u];i!=-;i=edge[i].nxt ) {
v=edge[i].to;
if ( !dfn[v] ) {
tarjan(v);
low[u]=min(low[u],low[v]);
}
else if ( vis[v] ) low[u]=min(low[u],dfn[v]);
}
if ( low[u]==dfn[u] ) {
scc++;
do {
v=stack[--top];
vis[v]=false;
belong[v]=scc;
num[scc]++;
}
while ( v!=u );
}
} void solve(int N)
{
memset(dfn,,sizeof(dfn));
memset(vis,false,sizeof(vis));
memset(num,,sizeof(num));
index=scc=top=;
for ( int i=;i<=N;i++ ) {
if ( !dfn[i] ) tarjan(i);
}
} void init()
{
tot=;
memset(head,-,sizeof(head));
} int main()
{
int n,m,i,j,k,x,y,z,x1,x2,y1,y2;
while ( scanf("%d%d",&n,&m)!=EOF ) {
init();
for ( i=;i<=m;i++ ) {
scanf("%d%d",&x,&y);
x++;y++;
if ( x>y ) swap(x,y);
arr[i].l=x;
arr[i].r=y;
}
for ( i=;i<=m;i++ ) {
for ( j=i+;j<=m;j++ ) {
x1=arr[i].l;
y1=arr[i].r;
x2=arr[j].l;
y2=arr[j].r;
if ( (x1<x2&&x2<y1&&y1<y2) || (x2<x1&&x1<y2&&y2<y1) ) {
addedge(i,j+m);
addedge(j,i+m);
addedge(i+m,j);
addedge(j,i+m);
}
}
}
solve(*m);
bool flag=true;
for ( i=;i<=m;i++ ) {
if ( belong[i]==belong[i+m] ) {
flag=false;
break;
}
}
if ( flag ) printf("panda is telling the truth...\n");
else printf("the evil panda is lying again\n");
}
return ;
}
POJ3207
4.(POJ3678)http://poj.org/problem?id=3678
题意:有n个数,m个操作。对于每个操作给出x y z op。op有三种操作,分别为AND OR XOR,该三种操作于计算机二进制中的% | ^三种操作相对于。对于每个操作表示的含义是第x个数和第y个数进行相应的操作使得结果为z。问是否存在这样的一组数使得所有的操作全都满足
分析:一个数的状态分为0(正面)和1(反面)。对于不同操作有不同的建边方式。
对于操作&来说,当z==1时x-x' y-y'(表示均选择反面)。当z==0时,x'-y y-x'(表示反面状态最多选一个)
对于操作|来说,当z==1时 x-y' y-x'(表示反面最少选一个)。当z==0时,x'-x y'-y(表示均选择正面)
对于操作^来说,当z==1时 x-y' y-x' x'-y y'-x(表示正面 反面都要选择一个)。当z==0时,x-y y-x x'-y' y'-x'(表示所选的两个状态需要相同)
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=;
const int maxm=5e6;
struct edge{
int to,nxt;
}edge[maxm];
int head[maxn],tot;
int low[maxn],dfn[maxn],stack[maxn],belong[maxn];
int index,top;
int scc;
bool vis[maxn];
int num[maxn]; void addedge(int u,int v)
{
edge[tot].to=v;
edge[tot].nxt=head[u];
head[u]=tot++;
} void tarjan(int u)
{
int v;
low[u]=dfn[u]=++index;
stack[top++]=u;
vis[u]=true;
for ( int i=head[u];i!=-;i=edge[i].nxt ) {
v=edge[i].to;
if ( !dfn[v] ) {
tarjan(v);
low[u]=min(low[u],low[v]);
}
else if ( vis[v] ) low[u]=min(low[u],dfn[v]);
}
if ( low[u]==dfn[u] ) {
scc++;
do {
v=stack[--top];
vis[v]=false;
belong[v]=scc;
num[scc]++;
}
while ( v!=u );
}
} void solve(int N)
{
memset(dfn,,sizeof(dfn));
memset(vis,false,sizeof(vis));
memset(num,,sizeof(num));
index=scc=top=;
for ( int i=;i<=N;i++ ) {
if ( !dfn[i] ) tarjan(i);
}
} void init()
{
tot=;
memset(head,-,sizeof(head));
} int main()
{
int n,m,i,j,k,x,y,z;
char s[];
while ( scanf("%d%d",&n,&m)!=EOF ) {
init();
while ( m-- ) {
scanf("%d%d%d%s",&x,&y,&z,s);
x++;y++;
if ( s[]=='A' ) {
if ( z== ) {
addedge(x,x+n);
addedge(y,y+n);
}
else {
addedge(x+n,y);
addedge(y+n,x);
}
}
else if ( s[]=='O' ) {
if ( z== ) {
addedge(x,y+n);
addedge(y,x+n);
}
else {
addedge(x+n,x);
addedge(y+n,y);
}
}
else if ( s[]=='X' ) {
if ( z== ) {
addedge(x,y+n);
addedge(y,x+n);
addedge(x+n,y);
addedge(y+n,x);
}
else {
addedge(x,y);
addedge(y,x);
addedge(x+n,y+n);
addedge(y+n,x+n);
}
}
}
solve(*n);
bool flag=true;
for ( i=;i<=n;i++ ) {
if ( belong[i]==belong[i+n] ) {
flag=false;
break;
}
}
if ( flag ) printf("YES\n");
else printf("NO\n");
}
return ;
}
POJ3678
5.(HDOJ4421)http://acm.hdu.edu.cn/showproblem.php?pid=4421
题意:给定一个b[N][N]的矩阵,它是由a[N]矩阵按相应的条件变换而来,问是否可能存在这样的a矩阵
分析:只需要将b[][]矩阵中的数拆成二进制(最多31位),就将这个问题转化成上面的那个问题,但是此题很容易TLE
6.(HDOJ1815)http://acm.hdu.edu.cn/showproblem.php?pid=1815
题意:给定n个牛棚,和两个中转点s1,s2,每个牛棚和中转点的坐标都是已知的,每个牛棚都要连接一个中转点,每个牛棚中的牛可以通过中转点到其他任意一个牛棚。现有两种关系,有点牛之间相互讨厌不能将它们连接到同一个中转点,有点牛相互喜欢必须将它们连接到同一个中转点。求使最长的牛棚间距离最小,两个牛棚之间的距离为曼哈顿距离,现要使最大值最小,求这个最小的最大值是多少。
分析:二分+s-sat。不断二分最小的最大值,再进行构图和2-sat,看是否有出现矛盾情况。对于每个牛棚正面代表与s1相连,反面代表与s2相连 。
对于like关系:x-y y-x x'-y' y'-x'(表示x和y要么同时选,要么同时不选)
对于hate关系:x-y' y-x' x'-y y'-x(表示x,y只能同时选择一个)
而对于两两牛棚之间,有四种关系(都连s1,都连2,一个连s1一个连s2)。此时考虑的是一定不连(>mid)的情况,而不是一定连(<=mid)的情况(其实是可能连,但是建边只能建成一定连)。
dis(i,S1)+dis(S1,j)>mid x-y' y-x'
dis(i,S2)+dis(S2,j)>mid x'-y y'-x
dis(i,S1)+dis(S1,S2)+dis(S2,j)>mid x-y y'-x'
dis(i,S2)+dis(S2,S1)+dis(S1,j)>mid x'-y' y-x
注意:预处理算出每个牛棚到两个中转点之间的距离。对于不存在的情况,当最后r==inf时(没有任何一种情况是满足条件的),输出-1。数组尽量开大
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;
const int maxn=;
const int maxm=;
const int inf=1e8;
struct edge{
int to,nxt;
}edge[maxm];
int head[maxn],tot;
int low[maxn],dfn[maxn],stack[maxn],belong[maxn];
int index,top;
int scc;
bool vis[maxn];
int num[maxn];
int A,B,d,n,dis1[maxn],dis2[maxn];
struct node{
int x,y;
}aa[maxn],bb[maxn],arr[maxn],s1,s2;
bool flag; void addedge(int u,int v)
{
edge[tot].to=v;
edge[tot].nxt=head[u];
head[u]=tot++;
} void tarjan(int u)
{
int v;
low[u]=dfn[u]=++index;
stack[top++]=u;
vis[u]=true;
for ( int i=head[u];i!=-;i=edge[i].nxt ) {
v=edge[i].to;
if ( !dfn[v] ) {
tarjan(v);
low[u]=min(low[u],low[v]);
}
else if ( vis[v] ) low[u]=min(low[u],dfn[v]);
}
if ( low[u]==dfn[u] ) {
scc++;
do {
v=stack[--top];
vis[v]=false;
belong[v]=scc;
num[scc]++;
}
while ( v!=u );
}
} void solve(int N)
{
memset(dfn,,sizeof(dfn));
memset(vis,false,sizeof(vis));
memset(num,,sizeof(num));
index=scc=top=;
for ( int i=;i<=N;i++ ) {
if ( !dfn[i] ) tarjan(i);
}
} void init()
{
tot=;
memset(head,-,sizeof(head));
} int dis(node a,node b)
{
return abs(a.x-b.x)+abs(a.y-b.y);
} bool judge(int mid)
{
int i,j,k,x,y,x1,x2,y1,y2;
init();
for ( i=;i<=A;i++ ) {
x=aa[i].x;
y=aa[i].y;
addedge(x,y+n);
addedge(y,x+n);
addedge(x+n,y);
addedge(y+n,x);
}
for ( i=;i<=B;i++ ) {
x=bb[i].x;
y=bb[i].y;
addedge(x,y);
addedge(y,x);
addedge(x+n,y+n);
addedge(y+n,x+n);
}
for ( i=;i<=n;i++ ) {
for ( j=i+;j<=n;j++ ) {
if ( dis1[i]+dis1[j]>mid ) {
addedge(i,j+n);
addedge(j,i+n);
}
if ( dis1[i]+d+dis2[j]>mid ) {
addedge(i,j);
addedge(j+n,i+n);
}
if ( dis2[i]+dis2[j]>mid ) {
addedge(i+n,j);
addedge(j+n,i);
}
if ( dis2[i]+d+dis1[j]>mid ) {
addedge(i+n,j+n);
addedge(j,i);
}
}
}
solve(*n);
for ( i=;i<=n;i++ ) {
if ( belong[i]==belong[i+n] ) return false;
}
return true;
} int main()
{
int m,i,j,k,x,y,z,l,r,mid;
while ( scanf("%d%d%d",&n,&A,&B)!=EOF ) {
scanf("%d%d%d%d",&s1.x,&s1.y,&s2.x,&s2.y);
d=dis(s1,s2);
for ( i=;i<=n;i++ ) {
scanf("%d%d",&arr[i].x,&arr[i].y);
dis1[i]=dis(arr[i],s1);
dis2[i]=dis(arr[i],s2);
}
for ( i=;i<=A;i++ ) scanf("%d%d",&aa[i].x,&aa[i].y);
for ( i=;i<=B;i++ ) scanf("%d%d",&bb[i].x,&bb[i].y);
l=;
r=inf;
while ( r-l> ) {
mid=(l+r)/;
if ( judge(mid) ) r=mid;
else l=mid;
}
if ( r!=inf ) printf("%d\n",r);
else printf("-1\n");
}
return ;
}
HDOJ1815
7.(POJ3648)http://poj.org/problem?id=3648
题意:新娘,新郎和n-1对夫妇,分别坐在长条桌的两边,其中有m对人之间存在奸情(同性和异性皆有可能)。现在有两个限制:1. 任意一对夫妇必须坐在长条桌的两面;
2. m对有奸情的人不能坐在新娘的对面;求一种可能的方案,使得上面两种情况都能满足。
分析:每对夫妇看作一个状态,正面(x)表示女的坐新娘对面,反面(x')表示男的坐新娘对面。首先连一条0-1表示新郎坐新娘对面。对于有奸情的2个人,最多只有一个人坐在新娘对面。最后输出拓朴排序靠后的(即belong[]大的)
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=;
const int maxm=;
struct edge{
int to,nxt;
}edge[maxm];
int head[maxn],tot;
int low[maxn],dfn[maxn],stack[maxn],belong[maxn];
int index,top;
int scc;
bool vis[maxn];
int num[maxn]; void addedge(int u,int v)
{
edge[tot].to=v;
edge[tot].nxt=head[u];
head[u]=tot++;
} void tarjan(int u)
{
int v;
low[u]=dfn[u]=++index;
stack[top++]=u;
vis[u]=true;
for ( int i=head[u];i!=-;i=edge[i].nxt ) {
v=edge[i].to;
if ( !dfn[v] ) {
tarjan(v);
low[u]=min(low[u],low[v]);
}
else if ( vis[v] ) low[u]=min(low[u],dfn[v]);
}
if ( low[u]==dfn[u] ) {
scc++;
do {
v=stack[--top];
vis[v]=false;
belong[v]=scc;
num[scc]++;
}
while ( v!=u );
}
} void solve(int N)
{
memset(dfn,,sizeof(dfn));
memset(vis,false,sizeof(vis));
memset(num,,sizeof(num));
index=scc=top=;
for ( int i=;i<N;i++ ) {
if ( !dfn[i] ) tarjan(i);
}
} void init()
{
tot=;
memset(head,-,sizeof(head));
} int main()
{
int n,m,i,j,k,x,y,z;
char c1,c2;
while ( scanf("%d%d",&n,&m)!=EOF && (n+m) ) {
init();
addedge(,);
for ( i=;i<=m;i++ ) {
scanf("%d%c %d%c",&x,&c1,&y,&c2);
x=*x;
if ( c1=='h' ) x^=;
y=*y;
if ( c2=='h' ) y^=;
addedge(x,y^);
addedge(y,x^);
}
solve(*n);
bool flag=true;
for ( i=;i<n;i++ ) {
if ( belong[i*]==belong[i*+] ) {
flag=false;
break;
}
}
if ( !flag ) {
printf("bad luck\n");
continue;
}
for ( i=;i<n;i++ ) {
x=*i;
y=*i+;
if ( belong[x]<belong[y] ) printf("%dh",i);
else printf("%dw",i);
if ( i!=n- ) printf(" ");
else printf("\n");
}
}
return ;
}
POJ3648
也可以将正面表示女的坐新郎对面,反面表示男的坐新郎对面。首先连一条1-0表示新娘坐新郎对面。对于有奸情的2个人,最少有一个人坐在新狼对面。最后输出拓扑排序小的
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=;
const int maxm=;
struct edge{
int to,nxt;
}edge[maxm];
int head[maxn],tot;
int low[maxn],dfn[maxn],stack[maxn],belong[maxn];
int index,top;
int scc;
bool vis[maxn];
int num[maxn]; void addedge(int u,int v)
{
edge[tot].to=v;
edge[tot].nxt=head[u];
head[u]=tot++;
} void tarjan(int u)
{
int v;
low[u]=dfn[u]=++index;
stack[top++]=u;
vis[u]=true;
for ( int i=head[u];i!=-;i=edge[i].nxt ) {
v=edge[i].to;
if ( !dfn[v] ) {
tarjan(v);
low[u]=min(low[u],low[v]);
}
else if ( vis[v] ) low[u]=min(low[u],dfn[v]);
}
if ( low[u]==dfn[u] ) {
scc++;
do {
v=stack[--top];
vis[v]=false;
belong[v]=scc;
num[scc]++;
}
while ( v!=u );
}
} void solve(int N)
{
memset(dfn,,sizeof(dfn));
memset(vis,false,sizeof(vis));
memset(num,,sizeof(num));
index=scc=top=;
for ( int i=;i<N;i++ ) {
if ( !dfn[i] ) tarjan(i);
}
} void init()
{
tot=;
memset(head,-,sizeof(head));
} int main()
{
int n,m,i,j,k,x,y,z;
char c1,c2;
while ( scanf("%d%d",&n,&m)!=EOF && (n+m) ) {
init();
addedge(,);
for ( i=;i<=m;i++ ) {
scanf("%d%c %d%c",&x,&c1,&y,&c2);
x=*x;
if ( c1=='h' ) x^=;
y=*y;
if ( c2=='h' ) y^=;
addedge(y^,x);
addedge(x^,y);
}
solve(*n);
bool flag=true;
for ( i=;i<n;i++ ) {
if ( belong[i*]==belong[i*+] ) {
flag=false;
break;
}
}
if ( !flag ) {
printf("bad luck\n");
continue;
}
for ( i=;i<n;i++ ) {
x=*i;
y=*i+;
if ( belong[x]>belong[y] ) printf("%dh",i);
else printf("%dw",i);
if ( i!=n- ) printf(" ");
else printf("\n");
}
}
return ;
}
POJ3648(2)
8.(POJ2296)http://poj.org/problem?id=2296
题意:给出以下二维坐标点,然后让你往平面上放正方形,点必须落在正方形上面边的中点或者下面边的中点,正方形不能重叠,可以共用边。问最大正方形边的边长。
分析:二分最大边长。对于某两个点,先判断两点之间的横坐标距离,如果<mid,那么考察两点之间的纵坐标的关系。
纵坐标关系分为三大类:
abs(y1-y2)==0 此时必定一个正方形向上,一个正方形向下
abs(y1-y2)<mid 此时分为y1<y2 || y2<y1,此时两个正方形也必定一个向上一个向下
abs(y1-y2)<2*mid 此时分为y1<y2 || y2<y1 ,此时两个正方形满足不同时面向对方即可
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
const int maxn=;
const int maxm=;
const int inf=1e6;
struct edge{
int to,nxt;
}edge[maxm];
int head[maxn],tot;
int low[maxn],dfn[maxn],stack[maxn],belong[maxn];
int index,top;
int scc,n;
bool vis[maxn];
int num[maxn];
struct node{
int x,y;
}arr[maxn]; void addedge(int u,int v)
{
edge[tot].to=v;
edge[tot].nxt=head[u];
head[u]=tot++;
} void tarjan(int u)
{
int v;
low[u]=dfn[u]=++index;
stack[top++]=u;
vis[u]=true;
for ( int i=head[u];i!=-;i=edge[i].nxt ) {
v=edge[i].to;
if ( !dfn[v] ) {
tarjan(v);
low[u]=min(low[u],low[v]);
}
else if ( vis[v] ) low[u]=min(low[u],dfn[v]);
}
if ( low[u]==dfn[u] ) {
scc++;
do {
v=stack[--top];
vis[v]=false;
belong[v]=scc;
num[scc]++;
}
while ( v!=u );
}
} void solve(int N)
{
memset(dfn,,sizeof(dfn));
memset(vis,false,sizeof(vis));
memset(num,,sizeof(num));
index=scc=top=;
for ( int i=;i<=N;i++ ) {
if ( !dfn[i] ) tarjan(i);
}
} void init()
{
tot=;
memset(head,-,sizeof(head));
} bool judge(int mid)
{
int i,j,k,x1,x2,y1,y2;
init();
for ( i=;i<=n;i++ ) {
for ( j=i+;j<=n;j++ ) {
x1=arr[i].x;
y1=arr[i].y;
x2=arr[j].x;
y2=arr[j].y;
if ( abs(x2-x1)<mid ) {
if ( y1==y2 ) {
addedge(i,j+n);
addedge(j,i+n);
addedge(i+n,j);
addedge(j+n,i);
}
else if ( abs(y2-y1)<mid ) {
if ( y2>y1 ) {
addedge(j,j+n);
addedge(i+n,i);
}
else {
addedge(i,i+n);
addedge(j+n,j);
}
}
else if ( abs(y2-y1)<*mid ) {
if ( y2>y1 ) {
addedge(j,i);
addedge(i+n,j+n);
}
else {
addedge(i,j);
addedge(j+n,i+n);
}
}
}
}
}
solve(*n);
for ( i=;i<=n;i++ ) {
if ( belong[i]==belong[i+n] ) return false;
}
return true;
} int main()
{
int T,i,j,k,m,x,y,z,l,r,mid;
scanf("%d",&T);
while ( T-- ) {
scanf("%d",&n);
for ( i=;i<=n;i++ ) {
scanf("%d%d",&arr[i].x,&arr[i].y);
}
l=;
r=inf;
while ( r-l> ) {
mid=(l+r)/;
if ( judge(mid) ) l=mid;
else r=mid;
}
printf("%d\n",l);
}
return ;
}
POJ2296
小结:首先需要明确一个状态的正反两面所代表的含义(正反两面只能独立存在,不能共存),其次要弄清楚不同状态之间的关系应当如何建边,建完边后跑tarjan,当出现矛盾时即意味着条件不能满足。同时2-sat的题目往往会与二分(看到最大/最小)结合出题。
专题训练之2-sat的更多相关文章
- DP专题训练之HDU 2955 Robberies
打算专题训练下DP,做一道帖一道吧~~现在的代码风格完全变了~~大概是懒了.所以.将就着看吧~哈哈 Description The aspiring Roy the Robber has seen a ...
- dp专题训练
****************************************************************************************** 动态规划 专题训练 ...
- bryce1010专题训练——LCT&&树链剖分
LCT&&树链剖分专题 参考: https://blog.csdn.net/forever_wjs/article/details/52116682
- DP专题训练之HDU 1087 Super Jumping!
Description Nowadays, a kind of chess game called "Super Jumping! Jumping! Jumping!" is ve ...
- DP专题训练之HDU 1506 Largest Rectangle in a Histogram
Description A histogram is a polygon composed of a sequence of rectangles aligned at a common base l ...
- DP专题训练之HDU 1231 最大连续子序列
Description 给定K个整数的序列{ N1, N2, ..., NK },其任意连续子序列可表示为{ Ni, Ni+1, ..., Nj },其中 1 <= i <= j < ...
- DP专题训练之HDU 1864 最大报销额
做DP一定要注意数组的大小,嗯,就是这样~ Description 现有一笔经费可以报销一定额度的发票.允许报销的发票类型包括买图书(A类).文具(B类).差旅(C类),要求每张发票的总额不得超过10 ...
- bzoj专题训练
//http://blog.csdn.net/PoPoQQQ/article/category/2542243
- 算法专题训练 搜索a-T3 Ni骑士(ni)
搞了半天八数码弄不出来就只好来打题解 这道题是在搜索a碰到的(链接: http://pan.baidu.com/s/1jG9rQsQ ) 感觉题目最大亮点就是这英文简写"ni", ...
- 图论专题训练1-D(K步最短路,矩阵连乘)
题目链接 /* *题目大意: *求出从i到j,刚好经过k条边的最短路; * *矩阵乘法的应用之一(国家队论文): *矩阵乘法不满足交换律,矩阵乘法满足结合律; *给定一个有向图,问从A点恰好走k步(允 ...
随机推荐
- 进度条加载与案例优化对比——python使用perf_count方法实现
本章我们将讨论python3 perf_counter()的用法及它的实际应用我从中选取两个python基于rquests库的爬虫实例代码源文件进行举例 Python3 perf_counter() ...
- leetcode-二叉树的层次遍历(Java)
给定一个二叉树,返回其按层次遍历的节点值. (即逐层地,从左到右访问所有节点). 例如:给定二叉树: [3,9,20,null,null,15,7], 3 / \ 9 20 / \ 15 7 返回其层 ...
- Visual Stdio Code编辑Mark Down
Visual Studio Code可以一边写Markdown一边预览了,而且不需要任何插件. 方法如下: 新建一个文件,以 .md 为后缀: Visual Studio Code 原生就支持高亮Ma ...
- numpy切片和布尔型索引
numpy 标签(空格分隔): numpy 数据挖掘 切片 数组切片是原始数组的视图.这意味着数据不会被复制,视图上的任何修改都会直接反映到源数组上 In [16]: arr Out[16]: arr ...
- .Net并行编程 - Reactive Extensions(Rx)并发浅析
关于Reactive Extensions(Rx) 关于Reactive Extensions(Rx),先来看一下来自微软的官方描述: The Reactive Extensions (Rx) is ...
- Windows下PATH等环境变量详解(转载)
本文转载自http://legend2011.blog.51cto.com/3018495/553255 在学习JAVA的过程中,涉及到多个环境变量(environment variable)的概念, ...
- VUE中关于表单提交的简单实现
main.js import Vue from "../vue.js"; import App from "./App.js"; //启动 new Vue({ ...
- “Hello World!团队”Alpha发布—视频链接+文案+美工
视频链接:http://v.youku.com/v_show/id_XMzEyNjc2MTAyOA==.html?sharefrom=iphone&sharekey=5378037f8b710 ...
- UCP协议
UDP只在ip数据报的服务上增加了一点功能,就是复用和分用还有差错检验的功能 (1)UDP是面向无连接:发送之前不需要建立连接,减少了时间延续 (2)UDP只是尽最大努力交付,不能保证无措 (3)UD ...
- 团队作业7——第二次项目冲刺(Beta版本)
团队作业7——第二次项目冲刺-Beta版本项目计划 团队作业7——第二次项目冲刺(Beta版本)-第一篇 团队作业7——第二次项目冲刺(Beta版本)-第二篇 团队作业7——第二次项目冲刺(Beta版 ...