poj3648:Wedding——題解(配2-SAT簡易講解)
http://poj.org/problem?id=3648
(在家,而且因為2-SAT寫的不明不白的,所以這篇詳細寫)
題目大意:
有一對新人結婚,邀請了n-1 對夫婦去參加婚禮。婚禮上所有人要坐在一張很長的桌子的兩邊。所有的夫婦不能坐在同一邊。還有m 對人,這對人不能同時坐在新郎一邊,但可以同時坐在新娘這邊或是分兩邊坐。
以這道題為2-SAT講解模板題。
(請先了解2-SAT是幹什麽的再往下看)
首先判斷:打眼一看一定是2-SAT。
然後建圖,a到b表示如果選了a就一定選b的意思。
那麽對於我們所給出的矛盾關系,發現a和b雖然不共邊,但是a一定和b的另一半共邊或著b一定和a的另一半共邊。於是利用上面的定義加邊。
這裏假設k為妻子,k+n為丈夫。
有一個需要加的就是(0,0+n)需要加邊(難點)
為什麽呢?因為我們能夠發現,只有新郎一邊是不能沖突的,新娘一邊隨意,那麽加上這條邊時如果我們取了新娘就一定會取新郎而導致錯誤,所以程序一定會去選擇新郎,由此我們得到了新郎那邊的座次。在那之後全部相反就能獲得新娘的座次了。
關鍵的2-SAT判沖突:
首先tarjan縮點,如果相互為夫妻的人在同一個強連通分量裏就說明錯誤。
然後按照拓撲序我們有:(以(a,b)為一對沖突為例,取a表示a與新郎共邊)
如果a所在的強連通分量(新圖中的點)的拓撲序在b(非a)所在的強連通分量之後,則a為真。(顯然取b就得取a而沖突,但是取a就可以不用取b避免沖突)
但是我們能夠發現Tarjan 算法所求的強連通分量就是按拓撲序的逆序得出的,所以我們直接用編號來表示,並不需要真的去拓撲……
舉個例子,比如這道題,明顯i與i+n沖突,to[i]表示i縮點編號,那我們有:
to[i]<to[i+n]時取i,反之取i+n。
但是這是新郎側編號,為了求新娘側的人,我們把上面的條件顛倒一下即可。
(PS:此題輸入有毒,如果不斷RE請原模原樣參照路由器的代碼的讀入寫,你就明白數據有什麽問題了)
#include<stack> #include<cstdio> #include<cstring> #include<vector> #include<iostream> #include<algorithm> using namespace std;const int N=1005; const int M=100005; struct node{ int to; int nxt; }edge[M*4]; int head[N*2],dfn[N*2],low[N*2],to[N*2]; int n,m,t,l,cnt; bool instack[N*2]; stack<int>q; inline void add(int u,int v){ cnt++; edge[cnt].to=v; edge[cnt].nxt=head[u]; head[u]=cnt; return; } inline int neg(int x){ if(x<=n)return x+n; return x-n; } void tarjan(int u){ t++; dfn[u]=t; low[u]=t; q.push(u); instack[u]=1; for(int i=head[u];i!=0;i=edge[i].nxt){ int 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(low[u]==dfn[u]){ int v; l++; do{ v=q.top(); q.pop(); instack[v]=0; to[v]=l; }while(v!=u); } return; } inline void clr(){ cnt=0;t=0;l=0; memset(dfn,0,sizeof(dfn)); memset(head,0,sizeof(head)); return; } int main(){ while(scanf("%d%d",&n,&m)!=EOF&&(n>0||m>0)){ clr(); for(int i=1;i<=m;i++){ int u,v;char a,b; scanf("%d%c %d%c",&u,&a,&v,&b); u++;v++; if(a==‘h‘)u+=n; if(b==‘h‘)v+=n; add(u,neg(v)); add(v,neg(u)); } add(1,n+1); for(int i=1;i<=n*2;i++){ if(!dfn[i])tarjan(i); } bool flag=1; for(int i=1;i<=n;i++){ if(to[i]==to[i+n]){ flag=0; break; } } if(!flag){ printf("bad luck\n"); continue; } for(int i=2;i<=n;i++){ printf("%d",i-1); if(to[i]>to[i+n])printf("w "); else printf("h "); } printf("\n"); } return 0; }
(敲了半個小時的同時搞懂了2-SAT,同時淩晨的城市真好看,好累……)
poj3648:Wedding——題解(配2-SAT簡易講解)