hdu 3715 hdu 1816 hdu 4115 (2-sat)
Posted Miracle_ma
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了hdu 3715 hdu 1816 hdu 4115 (2-sat)相关的知识,希望对你有一定的参考价值。
三个2-sat问题,我总结一下自己的经验,我还是太菜,好久没做2-sat,又不太会建图了
总结:2-sat问题的核心就是建模
建模的思想很重要:
1.首先要怎么才能看出来是2-sat问题,对于每一个点,都有两种选择,则可以考虑是2-sat
比如让你在n组里面选n个,每组2个东西,这样就是2-sat的模型
2.确定是2-sat问题之后,就是要确定,拥有两种选择的对象,比如下面的例题里,有的是x[i]的取值有0或者1两种,有的是一串钥匙有两个,但你只能选择其中一个,则对于一个钥匙,它的取值有取或者不取,而不是对于这一串,取第一个还是第二个, 还有个问题是石头剪刀布的输赢,对面出石头,你不输的话要出石头或者布,这就是两种选择
这个拥有两种选择的对象有n个,然后你建图就是考虑i和i+n,最后tarjan后找i和i+n是否矛盾。
3.建图,建图是核心问题,但是一旦能够确定2-sat的对象,我感觉建图还是相对容易的,只要细心,不少考虑情况即可
要找到题目给的必定的条件,比如a和b不能同时存在,则a->~b,b->~a,如果a和b必须同时存在,则a->b,b->a,这种都要靠自己的感觉,没有什么确定的答案
总而言之,虽然我有好多题都不会做,有的是自己想出来了,有的没有想出来,但是感觉2-sat的方法还是有套路可循的,就是确定问题,寻找对象,寻找其中的关系
下面来看例题把:
简单的就不赘述了
hdu 3715
http://acm.hdu.edu.cn/showproblem.php?pid=3715
给你一个程序的伪代码,以及a,b,c,三个数组,还有个位置的x数组,x数组元素值为0或者1,问你如何设置x数组的值,使能通过的层数最大
首先确定是2-sat问题,其次要求层数最大,二分即可,对于每次二分的层数mid,把1-mid给的a,b,c数组用于建图
有两种选择的对象是x数组,假设x[i]=0为点i,x[i]=1为点i+mid
c=0时,x[a[i]]和x[b[i]]不能同时为0,即a[i]->b[i]+mid ,b[i]->a[i]+x;
c=1时,a[i]->b[i],b[i]->a[i],a[i]+x->b[i]+x,b[i]+x->a[i]+x;
c=2时,a[i]+x->b[i],b[i]+x->a[i];
如此建图,跑tarjan判可行性,即可
#include <map> #include <set> #include <stack> #include <queue> #include <cmath> #include <string> #include <vector> #include <cstdio> #include <cctype> #include <cstring> #include <sstream> #include <cstdlib> #include <iostream> #include <algorithm> #pragma comment(linker,"/STACK:102400000,102400000") using namespace std; #define MAX 405 #define MAXN 1000005 #define maxnode 10 #define sigma_size 2 #define lson l,m,rt<<1 #define rson m+1,r,rt<<1|1 #define lrt rt<<1 #define rrt rt<<1|1 #define middle int m=(r+l)>>1 #define LL long long #define ull unsigned long long #define mem(x,v) memset(x,v,sizeof(x)) #define lowbit(x) (x&-x) #define pii pair<int,int> #define bits(a) __builtin_popcount(a) #define mk make_pair #define limit 10000 //const int prime = 999983; const int INF = 0x3f3f3f3f; const LL INFF = 0x3f3f; const double pi = acos(-1.0); const double inf = 1e18; const double eps = 1e-9; const LL mod = 1e9+7; const ull mxx = 1333331; /*****************************************************/ inline void RI(int &x) { char c; while((c=getchar())<'0' || c>'9'); x=c-'0'; while((c=getchar())>='0' && c<='9') x=(x<<3)+(x<<1)+c-'0'; } /*****************************************************/ struct Edge{ int u,v,next; }edge[MAXN]; int dfn[MAX],low[MAX],belong[MAX],sstack[MAX]; int head[MAX],tot,Index,top,cnt;// tot是建图//cnt是强联通分量个数 int n,m;//n是点对个数 //Index是tarjan序 int instack[MAX]; //top是tarjan的栈元素个数 int a[10005],b[10005],c[10005]; void init(){ mem(head,-1); mem(instack,0); mem(dfn,0); mem(belong,0); tot=0; top=0; cnt=0; Index=0; } void add_edge(int a,int b){ edge[tot]=(Edge){a,b,head[a]}; head[a]=tot++; } void tarjan(int u){//判断可行只需要一个tarjan即可 dfn[u]=low[u]=++Index; sstack[++top]=u; instack[u]=1; for(int i=head[u]; i!=-1; i=edge[i].next){ int v=edge[i].v; 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]){ ++cnt; while(1){ int k=sstack[top--]; instack[k]=0; belong[k]=cnt; if(k==u) break; } } } bool check(int x){ init(); for(int i=0;i<x;i++){ //if(a[i]==b[i]) continue; if(c[i]==0){ add_edge(a[i],b[i]+n); add_edge(b[i],a[i]+n); } else if(c[i]==1){ add_edge(a[i],b[i]); add_edge(b[i],a[i]); add_edge(a[i]+n,b[i]+n); add_edge(b[i]+n,a[i]+n); } else if(c[i]==2){ add_edge(a[i]+n,b[i]); add_edge(b[i]+n,a[i]); } } for(int i=0;i<2*n;i++){ if(!dfn[i]) tarjan(i); } for(int i=0;i<n;i++){ if(belong[i]==belong[i+n]) return false; } return true; } int main(){ //freopen("in.txt","r",stdin); int t; cin>>t; while(t--){ cin>>n>>m; for(int i=0;i<m;i++) scanf("%d%d%d",&a[i],&b[i],&c[i]); int l=1,r=m; while(l<=r){ int mid=(l+r)/2; //cout<<l<<" "<<r<<" "<<mid<<endl; if(check(mid)) l=mid+1; else r=mid-1; } cout<<r<<endl; } return 0; }
hdu 1816
http://acm.hdu.edu.cn/showproblem.php?pid=1816
给你n串钥匙,每串上面有2个钥匙,一共2n个钥匙,都互不相同,一串上一个如果用了,另一个就不能使用,然后给你m层,每层上面有两个锁,对应两种钥匙(可能相同)
问你最多能跑多少层?
2-sat+二分,这题两种选择对象有点难找,早上做的不太认真,一直GG
首先同一串钥匙上的两个a,b,每个钥匙有取或者不取两种选择,所以我们找到了问题的对线
a->~b,b->~a,但是这只是一部分限制条件,因为你每次二分的时候,mid层上的门,也是有限制的,这个限制比较难发现,因为有两个锁,你必须打开其中之一的锁,比如两个锁分别是c,d,那么你至少得打开一个,所以并不是选了c就不能选d,而应该是选了~c就不能选~d,所以建图是~c->d,~d->c
然后跑tarjan即可(题意好像有点小问题,他说的是有m个门,但是最后个门不能同向下面,我理解成了只有m-1个门GG)
#include <map> #include <set> #include <stack> #include <queue> #include <cmath> #include <string> #include <vector> #include <cstdio> #include <cctype> #include <cstring> #include <sstream> #include <cstdlib> #include <iostream> #include <algorithm> #pragma comment(linker,"/STACK:102400000,102400000") using namespace std; #define MAX 4445 #define MAXN 1000005 #define maxnode 10 #define sigma_size 2 #define lson l,m,rt<<1 #define rson m+1,r,rt<<1|1 #define lrt rt<<1 #define rrt rt<<1|1 #define middle int m=(r+l)>>1 #define LL long long #define ull unsigned long long #define mem(x,v) memset(x,v,sizeof(x)) #define lowbit(x) (x&-x) #define pii pair<int,int> #define bits(a) __builtin_popcount(a) #define mk make_pair #define limit 10000 //const int prime = 999983; const int INF = 0x3f3f3f3f; const LL INFF = 0x3f3f; const double pi = acos(-1.0); const double inf = 1e18; const double eps = 1e-9; const LL mod = 1e9+7; const ull mxx = 1333331; /*****************************************************/ inline void RI(int &x) { char c; while((c=getchar())<'0' || c>'9'); x=c-'0'; while((c=getchar())>='0' && c<='9') x=(x<<3)+(x<<1)+c-'0'; } /*****************************************************/ struct Edge{ int u,v,next; }edge[MAX*MAX*2]; int dfn[MAX],low[MAX],belong[MAX],sstack[MAX]; int head[MAX],tot,Index,top,cnt;// tot是建图//cnt是强联通分量个数 //Index是tarjan序 int instack[MAX]; //top是tarjan的栈元素个数 int aa[2500],bb[2500]; int c[2500],d[2500]; int pre[2500]; int n,m; void init(){ mem(head,-1); mem(instack,0); mem(dfn,0); mem(belong,0); tot=0; top=0; cnt=0; Index=0; } void add_edge(int a,int b){ edge[tot]=(Edge){a,b,head[a]}; head[a]=tot++; } void tarjan(int u){//判断可行只需要一个tarjan即可 dfn[u]=low[u]=++Index; sstack[++top]=u; instack[u]=1; for(int i=head[u]; i!=-1; i=edge[i].next){ int v=edge[i].v; 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]){ ++cnt; while(1){ int k=sstack[top--]; instack[k]=0; belong[k]=cnt; if(k==u) break; } } } bool check(int x){ init(); for(int i=0;i<n;i++){ add_edge(c[i],d[i]+2*n); add_edge(d[i],c[i]+2*n); } for(int i=1;i<=x;i++){ add_edge(bb[i]+2*n,aa[i]); add_edge(aa[i]+2*n,bb[i]); } for(int i=0;i<4*n;i++){ if(!dfn[i]) tarjan(i); } for(int i=0;i<2*n;i++){ if(belong[i]==belong[i+2*n]) return false; } return true; } int main(){ //freopen("in.txt","r",stdin); while(cin>>n>>m){ if(n==0&&m==0) break; for(int i=0;i<n;i++) scanf("%d%d",&c[i],&d[i]); for(int i=1;i<=m;i++){ scanf("%d%d",&aa[i],&bb[i]); } int l=1,r=m; while(l<=r){ int mid=(l+r)/2; if(check(mid)) l=mid+1; else r=mid-1; } cout<<r<<endl; } return 0; }
hdu 4115
http://acm.hdu.edu.cn/showproblem.php?pid=4115
两个人石头剪刀布,一共n轮,m个限制条件,已知第一个人n次出的是什么,问你在限制下,第二个人可能n轮都不输么,限制条件是某两次出的必须一样,或者必须不一样
做的时候还没总结到这么多,做完这题瞬间对2sat有了更多理解,首先这题如何看出来是2sat呢,对于n轮,你都不能输,所以你每轮都有两个出的方法,所以是两种选择,即为c,d,其次对象是每一轮,最后就是找每轮里的矛盾(两种都出了)。
建图嘛就简单了呗,根据条件,如果a,b轮必须相同,if c[a]!=c[b]...,if c[a]!=d[b],... if d[a]!=c[b]... if d[a]!=d[b] ...
如果必须不同 if c[a]==c[b]... if c[a]==d[b]... if d[a]==c[b]... if d[a]==d[b]...
考虑这样是否涵盖了所有的限制条件,我可以说是的,每一轮里的每个都和另一轮里的两个都讨论过了,所以是全面的考虑
通过这两题,我对2-sat建图的方法又有了更深刻的理解,原来只是理解了简单的建图,会套模版而已
改天再战
#include <map> #include <set> #include <stack> #include <queue> #include <cmath> #include <string> #include <vector> #include <cstdio> #include <cctype> #include <cstring> #include <sstream> #include <cstdlib> #include <iostream> #include <algorithm> #pragma comment(linker,"/STACK:102400000,102400000") using namespace std; #define MAX 20005 #define MAXN 1000005 #define maxnode 10 #define sigma_size 2 #define lson l,m,rt<<1 #define rson m+1,r,rt<<1|1 #define lrt rt<<1 #define rrt rt<<1|1 #define middle int m=(r+l)>>1 #define LL long long #define ull unsigned long long #define mem(x,v) memset(x,v,sizeof(x)) #define lowbit(x) (x&-x) #define pii pair<int,int> #define bits(a) __builtin_popcount(a) #define mk make_pair #define limit 10000 //const int prime = 999983; const int INF = 0x3f3f3f3f; const LL INFF = 0x3f3f; const double pi = acos(-1.0); const double inf = 1e18; const double eps = 1e-9; const LL mod = 1e9+7; const ull mxx = 1333331; /*****************************************************/ inline void RI(int &x) { char c; while((c=getchar())<'0' || c>'9'); x=c-'0'; while((c=getchar())>='0' && c<='9') x=(x<<3)+(x<<1)+c-'0'; } /*****************************************************/ struct Edge{ int u,v,next; }edge[MAX*8]; int dfn[MAX],low[MAX],belong[MAX],sstack[MAX]; int head[MAX],tot,Index,top,cnt;// tot是建图//cnt是强联通分量个数 //Index是tarjan序 int instack[MAX]; //top是tarjan的栈元素个数 int c[10005],d[10005]; int n,m; void init(){ mem(head,-1); mem(instack,0); mem(dfn,0); mem(belong,0); tot=0; top=0; cnt=0; Index=0; } void add_edge(int a,int b){ edge[tot]=(Edge){a,b,head[a]}; head[a]=tot++; } void tarjan(int u){//判断可行只需要一个tarjan即可 dfn[u]=low[u]=++Index; sstack[++top]=u; instack[u]=1; for(int i=head[u]; i!=-1; i=edge[i].next){ int v=edge[i].v; 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]){ ++cnt; while(1){ int k=sstack[top--]; instack[k]=0; belong[k]=cnt; if(k==u) break; } } } int main(){ //freopen("in.txt","r",stdin); int t,kase=0; cin>>t; while(t--){ int n,m; cin>>n>>m; init(); for(int i=1;i<=n;i++){ int x; scanf("%d",&x); if(x==1){ c[i]=1; d[i]=2; } else if(x==2){ c[i]=2; d[i]=3; } else if(x==3){ c[i]=3; d[i]=1; } } for(int i=0;i<m;i++){ int x,y,z; scanf("%d%d%d",&x,&y,&z); if(z==0){ if(c[x]!=c[y]){ add_edge(x,y+n); add_edge(y,x+n); } if(c[x]!=d[y]){ add_edge(x,y); add_edge(y+n,x+n); } if(d[x]!=d[y]){ add_edge(x+n,y); add_edge(y+n,x); } if(d[x]!=c[y]){ add_edge(x+n,y+n); add_edge(y,x); } } else{ if(c[x]==c[y]){ add_edge(x,y+n); add_edge(y,x+n); } if(d[x]==d[y]){ add_edge(x+n,y); add_edge(y+n,x); } if(c[x]==d[y]){ add_edge(x,y); add_edge(y+n,x+n); } if(d[x]==c[y]){ add_edge(x+n,y+n); add_edge(y,x); } } } for(int i=1;i<=2*n;i++){ if(!dfn[i]) tarjan(i); } int flag=0; for(int i=1;i<=n;i++){ if(belong[i]==belong[i+n]) flag=1; } kase++; printf("Case #%d: ",kase); if(flag) printf("no\n"); else printf("yes\n"); } return 0; }
以上是关于hdu 3715 hdu 1816 hdu 4115 (2-sat)的主要内容,如果未能解决你的问题,请参考以下文章
所有的畅通工程[HDU1232][HDU1874][HDU1875][HDU1879]
BestCoder Round #77 (div.2)(hdu5650,hdu5651(逆元),hdu5652(二分),hdu5653(dp))