/*
KM 二分圖匹配 MCMF DP 搜尋 好題
題意:在8*8棋盤上,給出8個皇后的初始位置,要求移動這8個皇后使得皇后之間互不攻擊。皇后移動一步可以沿直線或對角線移動任意個格子,但不>能跨過一個原本有皇后的格,即會被有皇后的格擋住。皇后的攻擊方式跟移動方式一樣的。 問最小移動總步數。
思路:neko最初給我看的時候我第一反應是狀壓DP,但感覺很複雜,搞不動。
他說只要能證明一個結論,這道題就很簡單了。要證:存在一種最終布局,使得最優移動過程中任何有皇后不互擋,也就是說我們可以不用考慮“不能跨皇后”這個限制,直接“穿越”做。
我畫了個4*4的圖,然後幸運地得出了證明:假設現在在i點的皇后要到k點去(那k點當然是最終布局的皇后點啦,不然到那裡去幹嘛),中間有個j(j點有個皇后)隔住了。 那麼k與j就在一條直線上,也就是說 j 不是最終皇后點,那麼 j 就應該要挪到她相應的點去,等她挪完後就不擋住i 到達 k 點了。
證了上述結論就好辦了,用二分圖帶權匹配KM演算法或者最大流最小費用流MCMF都可以做,費用為移動步數。
我是用KM 做的。先預先處理出8*8上所有合理布局,一共92種。然後對於每種合理布局,拿來跟初始布局搞二分圖帶權匹配。易知這個二分圖是完
全二分圖(表述不太準確,就是X部Y部兩兩有邊)。
這道題是歸類在DP上的 orz... 在vj上有人用搜尋+dp做...
*/
//lightoj 1061
#include <limits.h>#include <stdio.h>#include <algorithm>#include <string.h>using namespace std;#define N 8#define ABS(x) ((x) > 0 ? (x) : (-(x)))//bool flag = false; //for debugint l[3][N*2], X[N], map[100][N], a[N][2]; //map[i][x] 存第i種合理布局的x行皇后應該放的列數int edge[N][N], LX[N], LY[N], link[N], slack[N];bool visx[N], visy[N];int Top = 0;void label(int row, int col){ l[0][col] ^= 1; l[1][row+col] ^= 1; l[2][row-col+N] ^= 1;}bool check(int row, int col){ return l[0][col] && l[1][row+col] && l[2][row-col+N];}void init(int row){ if(row == N) { for(int i = 0; i < N; i++) map[Top][i] = X[i]; Top++; return ; } for(int i = 0; i < N; i++) { if(check(row, i)) { X[row] = i; label(row, i); init(row+1); label(row, i); } }}inline char rdc() { scanf(" "); return getchar(); }void read_board(){ int k = 0; for(int i = 0; i < N; i++) for(int j = 0; j < N; j++) if(rdc() == 'q') a[k][0] = i, a[k][1] = j, k++;}int dis(int x1, int y1, int x2, int y2){ if(x1 == x2 && y1 == y2) return 0; if(x1 == x2 || y1 == y2) return 1; if(x1 + y1 == x2 + y2 || (x1 - y1) == (x2 - y2)) return 1; //妹的,本來寫的是對的,看了neko的,然後改錯了=_= return 2;}void build(int * map) { for(int i = 0; i < N; i++) { int x = a[i][0], y = a[i][1]; for(int j = 0; j < N; j++) { edge[i][j] = -dis(x, y, j, map[j]); } }}bool find(int u){ visx[u] = true; for(int v = 0; v < N; v++) if(!visy[v]) { int rest = LX[u] + LY[v] - edge[u][v]; if(rest == 0) { visy[v] = true; if(link[v] == -1 || find(link[v])) { link[v] = u; return true; } } else slack[v] = min(slack[v], rest); } return false;}int KM(){ memset(LX, 128, sizeof(LX)); memset(LY, 0, sizeof(LY)); memset(link,-1, sizeof(link)); for(int i = 0; i < N; i++) for(int j = 0; j < N; j++) LX[i] = max(LX[i], edge[i][j]); for(int start = 0; start < N; start ++) { for(int i = 0; i < N; i++) slack[i] = (INT_MAX); while(true) { memset(visx, false, sizeof(visx)); memset(visy, false, sizeof(visy)); if(find(start)) break; else { int d = (INT_MAX); for(int i = 0; i < N; i++) if(!visy[i]) d = min(d, slack[i]); //if(d == (INT_MAX)) return -1; for(int i = 0; i < N; i++) if(visx[i]) LX[i] -= d; for(int i = 0; i < N; i++) if(visy[i]) LY[i] += d; else slack[i] -= d; } } } int sum = 0; for(int i = 0; i < N; i++) sum += edge[link[i]][i]; return -sum;}int main(){ int cases, Cas = 0; for(int i = 0; i < 3; i++) for(int j = 0; j < 2*N; j++) l[i][j] = 1; init(0); scanf("%d", &cases); while(cases--) { read_board(); int ans = 17; for(int i = 0; i < Top; i++) { //枚舉每一種最終布局 build(map[i]); //if(KM() == 6 && ans != 6) { // for(int j = 0; j < N; j++) printf("(%d,%d) ", a[j][0]+1,a[j][1]+1); // printf("\n"); // for(int j = 0; j < N; j++) printf("(%d,%d) ", j+1, 1+map[i][j]); // printf("\n"); //} ans = min(ans, KM()); } printf("Case %d: %d\n", ++Cas, ans); } return 0;}