甚麼是精確覆蓋問題?
$_{註1}$舉個例子,給你一堆$<2^{n}$的數字,叫你選出幾個,使得他們AND (&) 起來是0,OR (|) 起來是$2^{n}-1$
請用二近位的角度去想
這題轉換後會變成一個$N$行$M$列,每列有2個1 (每條邊的兩端點) 的矩陣
第一個範測轉換後變成以下矩陣:
1100
0110
0011
1001
第二個範測轉換後變成以下矩陣:
1100
0110
0011
如果還沒看出我們要幹嘛,請再看一次$_{註1}$
這種問題我想直接dfs試誤枚舉耶
每挑一列就把和那列的1有衝突的每一列都刪掉,失敗了再慢慢把每一列復原,挑另一列繼續做
當剛好全部覆蓋時就找到答案了
怎麼優化?
對於同一行,都剛好只會有一個1被選到,因此只要選這些1出現的那幾列就好了,然後就可以直接return,不用找另外一行
每一次做抉擇時,我們可以直接選擇1最少的那一行,這樣可以最小化分枝 (嘗試次數最少)
可是刪除一列需要$O(M*N)$的時間耶
用鏈表也需要$O(N)$......
那些0好煩Orz
那鏈表裡面不要把0存起來就好啦
這樣刪除一列只需要涉及2個1,形同$O(1)$刪列!
這就是舞蹈鏈 ($Dancing\ Links$) 的思想
如果自己實踐出了問題,可以看這篇確認想法
或者這篇確認操作過程
有些觀念可能遭誤解,因此建議和這裡比對一下
時間複雜度:無法確定 (上界應該是$O(M^{N})$,不過實際執行時間幾乎都是遠小於這個數字,像我這份code跑$20ms$超快www)
code:
#include<cstdio> #include<cassert> #include<vector> #include<algorithm> #define debug(x,...) //printf(x,##__VA_ARGS__) using namespace std; struct Edge { int a,b; Edge(){} Edge(const int _a,const int _b):a(min(_a,_b)),b(max(_a,_b)){} bool operator<(const Edge &e)const{return a!=e.a?a<e.a:b<e.b;} }; vector<Edge>EDGE; struct Node { Node *left,*right,*up,*down; const int r,c; Node(const int _r,const int _c):left(this),right(this),up(this),down(this),r(_r),c(_c){} }*COL[1000],*ROW[10000],*ORIGIN=new Node(-1,-1); void InsertH(Node *l,Node *o,Node *r){l->right=o,o->right=r,r->left=o,o->left=l;} void InsertV(Node *u,Node *o,Node *d){u->down=o,o->down=d,d->up=o,o->up=u;} int CNT[1000]; void AddNode(const int r,const int c) { Node *o=new Node(r,c); InsertH(ROW[r]->left,o,ROW[r]),InsertV(COL[c]->up,o,COL[c]); CNT[c]++; } int N,M; bool IsConnectedV(Node *o) { assert((o->up->down==o)==(o->down->up==o)); return (o->up->down==o); } void DisconnectV(Node *o){debug("unlinkV(%d,%d)\n",o->r,o->c);assert(IsConnectedV(o));o->up->down=o->down,o->down->up=o->up;CNT[o->c]--;assert(!IsConnectedV(o));} void ConnectV(Node *o){debug(" linkV(%d,%d)\n",o->r,o->c);assert(!IsConnectedV(o));o->up->down=o->down->up=o;CNT[o->c]++;assert(IsConnectedV(o));} void RemoveRow(const int r) { debug("remove row%d\n",r); Node *row=ROW[r],*cur=row; do{DisconnectV(cur),cur=cur->right;}while(cur!=row); } void RestoreRow(const int r) { debug("restore row%d\n",r); Node *row=ROW[r],*cur=row; do{ConnectV(cur),cur=cur->right;}while(cur!=row); } bool IsConnectedH(Node *o) { assert((o->left->right==o)==(o->right->left==o)); return (o->left->right==o); } void DisconnectH(Node *o){debug("unlinkH(%d,%d)\n",o->r,o->c);assert(IsConnectedH(o));o->left->right=o->right,o->right->left=o->left;assert(!IsConnectedH(o));} void ConnectH(Node *o){debug(" linkH(%d,%d)\n",o->r,o->c);assert(!IsConnectedH(o));o->left->right=o->right->left=o;assert(IsConnectedH(o));} void RemoveCol(const int c){debug("remove col%d\n",c);DisconnectH(COL[c]);}//為避免資訊遺失,只移除表頭 void RestoreCol(const int c){debug("restore col%d\n",c);ConnectH(COL[c]);}//為避免資訊遺失,只移除表頭 void SelectRow(const int r) { debug(" select %d\n",r); Node *row=ROW[r]; assert(IsConnectedV(row)); for(Node *cur1=row->right;cur1!=row;cur1=cur1->right) { assert(IsConnectedV(cur1)); for(Node *cur2=cur1->down;cur2!=cur1;cur2=cur2->down)if(cur2->r!=-1)RemoveRow(cur2->r),ConnectV(cur2);//為避免資訊遺失,cur2要接回來 } for(Node *cur1=row->right;cur1!=row;cur1=cur1->right)RemoveCol(cur1->c); RemoveRow(r); } void UnselectRow(const int r) { debug("unselect %d\n",r); RestoreRow(r); Node *row=ROW[r]; assert(IsConnectedV(row)); for(Node *cur1=row->left;cur1!=row;cur1=cur1->left)RestoreCol(cur1->c); for(Node *cur1=row->left;cur1!=row;cur1=cur1->left)//刪除時往右,反操作要往左 { assert(IsConnectedV(cur1)); for(Node *cur2=cur1->up;cur2!=cur1;cur2=cur2->up)if(cur2->r!=-1)DisconnectV(cur2),RestoreRow(cur2->r);//反向操作,刪除時往下,反操作要往上 } } int Dance(vector<int>&now,vector<int>&ans) { if(ORIGIN->right==ORIGIN) { if(ORIGIN->down==ORIGIN){ans=now;return 1;} else{return 2;} } Node *col=ORIGIN; for(Node *cur=ORIGIN->right;cur!=ORIGIN;cur=cur->right) { if(CNT[cur->c]==0)return 0; if(col==ORIGIN||CNT[cur->c]<CNT[col->c])col=cur; } assert(col!=ORIGIN); int solution_count=0; for(Node *row=col->down;row!=col&&solution_count<=1;row=row->down) { now.push_back(row->r); SelectRow(row->r); solution_count+=Dance(now,ans); UnselectRow(row->r); now.pop_back(); } return solution_count; } int main() { // freopen("in.txt","r",stdin); for(int i=0;i<1000;i++)COL[i]=NULL; for(int i=0;i<10000;i++)ROW[i]=NULL; int testcount;scanf("%d",&testcount); while(testcount--) { scanf("%d%d",&N,&M); for(int i=0;i<N;i++)if(COL[i])delete COL[i]; for(int i=0;i<M;i++)if(ROW[i])delete ROW[i]; delete ORIGIN;ORIGIN=new Node(-1,-1); for(int i=0;i<N;i++)COL[i]=new Node(-1,i),InsertH(ORIGIN->left,COL[i],ORIGIN),CNT[i]=0; for(int i=0;i<M;i++)ROW[i]=new Node(i,-1),InsertV(ORIGIN->up,ROW[i],ORIGIN); EDGE.clear(); for(int i=0,a,b;i<M;i++)scanf("%d%d",&a,&b),EDGE.push_back(Edge(--a,--b)); sort(EDGE.begin(),EDGE.end()); for(int i=0;i<M;i++) { const Edge &e=EDGE[i]; AddNode(i,e.a),AddNode(i,e.b); } vector<int>now,ans; const int result=Dance(now,ans);//全部主要計算都在這一行 //確認一下舞蹈鏈有沒有受損 for(int i=0;i<N;i++)assert(IsConnectedH(COL[i])); for(int i=0;i<M;i++) { assert(IsConnectedV(ROW[i])); int j=1; for(Node *cur=ROW[i]->right;cur!=ROW[i];cur=cur->right,j++) { assert(IsConnectedV(cur)); if(j==1)assert(cur->c==EDGE[i].a); else if(j==2)assert(cur->c==EDGE[i].b); else assert(0); } } //損害確認通過 if(result==1) { puts("YES"); sort(ans.begin(),ans.end()); for(const int ei:ans)printf("%d %d\n",EDGE[ei].a+1,EDGE[ei].b+1); } else puts("NO"); } return 0; }
沒有留言:
張貼留言
歡迎留言或問問題~
若您的留言中包含程式碼,請參考這篇
如果留言不見了請別慌,那是因為被google誤判成垃圾留言,小莫會盡快將其手動還原
注意:只有此網誌的成員可以留言。