poj-3279 poj-1753(二進位制列舉)
題目連結:http://poj.org/problem?id=3279
題目大意:
有一個m*n的棋盤(1 ≤ M ≤ 15; 1 ≤ N ≤ 15),每個格子有兩面分別是0或1,每次可以對一個格子做一次翻轉操作,將被操作的格子和與其相鄰的周圍4個格子都會進行翻轉。問做少做多少次翻轉可以將所有格子翻轉成0,輸出翻轉方案(每個棋子的翻轉次數)。沒有方案時輸出“IMPOSSIBLE”。
Sample Input
4 4 1 0 0 1 0 1 1 0 0 1 1 0 1 0 0 1
Sample Output
0 0 0 0 1 0 0 1 1 0 0 1 0 0 0 0
解題思路:
首先需要明確每個格子只有兩種情況,就是要麼翻和要麼不翻,因為翻奇數次都與翻1次,而翻偶數次等於沒翻。再來看一下資料範圍n和m最大都為15,範圍不是很大,但是全部列舉的話應該也會超時,所以我們要找到一個方案減少列舉量,我們發現如果一行的狀態已經確定了,那麼它接下來的是不是都確定了。下一行都要保證上一行全為0就可以了,最後只需要判斷一下最後一行是否都為0就可以了,而第一行有n個棋子,它的狀態也就是有2^n種。我們可以用一個含有16位的二進位制數來表示它的狀態,如果第i位為1表示該格子需要翻轉,如果為0,則表示不翻轉,翻轉完第一行,僅接著翻第2行,每行的狀態由其上一行的狀態所確定。這樣只要判斷最後一行是否可以全部為0即可,如果為0,判斷是否比答案更小,如果更小,則對答案進行更新。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<vector> #include<string> #include<set> #include<cmath> #include<list> #include<deque> #include<cstdlib> #include<bitset> #include<stack> #include<map> #include<queue> using namespace std; typedef long long ll; const int INF=0x3f3f3f3f; const double PI=acos(-1.0); const double eps=1e-6; const ll mod=1e9+7; ll gcd(ll a,ll b){return b?gcd(b,a%b):a;} int n,m,mp[20][20],tmp[20][20],cnt[20][20],ans[20][20],res; void flip(int x,int y) //翻轉,用異或符操作 { tmp[x][y]^=1; tmp[x+1][y]^=1; tmp[x-1][y]^=1; tmp[x][y+1]^=1; tmp[x][y-1]^=1; } int main() { ios_base::sync_with_stdio(false); cin.tie(0); cin>>m>>n; for(int i=1;i<=m;i++) { for(int j=1;j<=n;j++) { cin>>mp[i][j]; } } res=INF; //初始答案為無窮大 for(int s=0;s<(1<<n);s++) //列舉第一行的所有狀態 { int tot=0; memset(cnt,0,sizeof(cnt)); for(int i=1;i<=m;i++) { for(int j=1;j<=n;j++) { tmp[i][j]=mp[i][j]; } } for(int i=0;i<n;i++) { if(((s>>i)&1)) //如果第i+1位為1,則對其進行翻轉 { tot++; cnt[1][i+1]=1; flip(1,i+1); } } for(int i=2;i<=m;i++) //翻轉第2至m行 { for(int j=1;j<=n;j++) { if(tmp[i-1][j]) { tot++; cnt[i][j]=1; flip(i,j); } } } int flag=1; for(int i=1;i<=n;i++) //判斷是否合法 { if(tmp[m][i]!=0) { flag=0; break; } } if(flag&&tot<res) //合法且比當前答案小對答案進行更新 { res=tot; for(int i=1;i<=m;i++) { for(int j=1;j<=n;j++) { ans[i][j]=cnt[i][j]; } } } } if(res==INF) //不能實現 { cout<<"IMPOSSIBLE"<<endl; return 0; } for(int i=1;i<=m;i++) { for(int j=1;j<=n;j++) { if(j==1) cout<<ans[i][j]; else cout<<" "<<ans[i][j]; } cout<<endl; } return 0; }
題目連結:http://poj.org/problem?id=1753
題目大意:
給你一個4*4的棋盤,上面擺了16個棋子,每個棋子有兩面,一面是黑色一面是白色,給出棋盤的初始狀態,每次可以翻一個棋子,而且每次翻的時候連著四周的棋子一起翻,問最少幾次操作可以使棋盤全變為白棋或者黑棋。
思路:
首先可以用DFS做,資料範圍較小,最多翻16個棋子(每個棋子要麼翻要麼不翻,再翻就和原來一樣了),列舉翻的棋子數,然後用DFS搜尋翻該棋子數的所有情況,然後進行判斷是否全為黑或全為白,就可以找出答案。
當然也可以用二進位制列舉的方法,總共16個棋子,對應2^16種狀態,直接暴力列舉全部列舉全部情況,保留答案的最小值,與上面那題類似。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<vector> #include<string> #include<set> #include<cmath> #include<list> #include<deque> #include<cstdlib> #include<bitset> #include<stack> #include<map> #include<queue> using namespace std; typedef long long ll; const int INF=0x3f3f3f3f; const double PI=acos(-1.0); const double eps=1e-6; const ll mod=1e9+7; ll gcd(ll a,ll b){return b?gcd(b,a%b):a;} int mp[5][5],tmp[5][5],ans; void flip(int x,int y) //翻轉 { tmp[x][y]^=1; tmp[x+1][y]^=1; tmp[x-1][y]^=1; tmp[x][y+1]^=1; tmp[x][y-1]^=1; } int main() { ios_base::sync_with_stdio(false); cin.tie(0); for(int i=1;i<=4;i++) { for(int j=1;j<=4;j++) { char c; cin>>c; if(c=='w') mp[i][j]=0; else mp[i][j]=1; } } ans=17; for(int s=0;s<(1<<16);s++) //列舉所有情況 { int tot=0; for(int i=1;i<=4;i++) { for(int j=1;j<=4;j++) tmp[i][j]=mp[i][j]; } for(int i=0;i<16;i++) { if((s>>i)&1) { int x=(i+1)/4+1; //判斷第i+1棋子的座標,然後對其翻轉 int y=(i+1)%4; if(y==0){x--;y=4;} tot++; flip(x,y); } } int flag=1; for(int i=1;i<=4;i++) //判斷是否合法 { for(int j=1;j<=4;j++) { if(tmp[i][j]!=tmp[1][1]) { flag=0; break; } } if(flag==0) break; } if(flag&&tot<ans) //合法更新答案 ans=tot; } if(ans==17) cout<<"Impossible"<<endl; else cout<<ans<<endl; return 0; }