POJ3276
题意:
N 头牛排成一列,每头牛或向前或向后。每次可以反转连续的 K 头牛,求出让所有的牛都能面向前方所需要的最少操作次数 M 和对应的最小的 K。
解法:
首先加入顺序的枚举每个以向后的牛开始的区间,让其反转,之后检查可行性,复杂度为 $O(n^3)$,即顺序遍历、反转、寻找第一个面向正面的奶牛,不可取。
所以这种问题的关键就是怎样优化区间反转的那一部分。
设:$f[i] = $区间$[i,i+K-1]$进行了反转的话则为$1$,否则为$0$.
这样在考虑第 i 头牛的时候,如果 $sum_{j=i-K+1}^{i-1}f[j]$为奇数的话,则这头牛的方向与起始方向是相反的,否则方向不变
又因为:$sum_{j=(i+1)-K+1}^{i}f[j]=sum_{j=i-K+1}^{i-1}f[j]+f[i]-f[i-K+1]$
这样每个区间的牛的反转情况就可以在$O(1)$的时间内算出来,可解。
1 int N; 2 int dir[MAXN]; // 牛的方向(0:F,1:B) 3 int f[MAXN]; //区间(i,i-K+1)是否进行反转 4 5 //固定K,求对应的最小操作回数 6 //无解返回-1 7 int calc(int K) { 8 MEM(f, 0); 9 int res = 0; 10 int sum = 0; // 包含点i的所有区间f的和 11 for (int i = 0; i + K <= N; i++) { 12 if ((dir[i] + sum) % 2 != 0) { 13 // 前端的牛朝后方 14 res++; 15 f[i] = 1; 16 } 17 sum += f[i]; 18 if (i - K + 1 >= 0) sum -= f[i - K + 1];//向前推进区间,减掉之前区间的头 19 } 20 //单独检查剩下的牛是否有朝后方的情况 21 for (int i = N - K + 1; i < N; i++) { 22 if ((dir[i] + sum) % 2 != 0) return -1; //无解 23 if (i - K + 1 >= 0) sum -= f[i - K + 1]; 24 } 25 return res; 26 } 27 // M为最小操作次数,K为对应的反转的区间长度 28 void solve() { 29 int K = 1, M = N; 30 for (int k = 1; k <= N; k++) { 31 int m = calc(k); 32 if (m > 0 && M > m) { 33 M = m; 34 K = k; 35 } 36 } 37 printf("%d %d ", K, M); 38 } 39 40 int main() { 41 #ifndef ONLINE_JUDGE 42 freopen("input.txt", "r", stdin); 43 #endif 44 scanf("%d", &N); 45 REP(i, 0, N - 1) { 46 char c[2]; 47 scanf("%s", c); 48 dir[i] = (c[0] == 'F') ? 0 : 1; 49 } 50 solve(); 51 return 0; 52 }
POJ3185
题意:一共有一行20个数,每个数字都是0或1,每次翻一个数都会将这个数两边的数取反,保证有解,求最少的反转次数
解法:其实就是K为3的开关反转问题,不同的是,在之前我们是把连续三个数按照左顶点推进,这样在 $i+k<=N$ 的时候就要退出了,但是这里不同的是,可以在 $i+k<=N+1$ 的时候再退出,因为驱动反转的是三个的中间那一个,具体的可以看下面的代码:
1 int N = 20; 2 int dir[MAXN]; 3 int f[MAXN]; 4 5 // 0, 1 6 bool CanFlip(int i, int sum) { return (dir[i] + sum) & 1; } 7 8 int solve() { 9 MEM(f, 0); 10 int res = 0, sum = 0; 11 int K = 3; 12 // 在开关问题的时候,这里是 i+K<=N,为什么? 13 // 假如 K=3,那么当 i=18 14 // 的时候,他就不能翻了,(这也是为什么在判断最后几位的时候没有sum+=f[i]). 15 // 但是这里当 i=18 的时候还可以翻, 所以往前推一位 16 for (int i = 0; i + K <= N + 1; i++) { 17 if (CanFlip(i, sum)) { 18 res++; 19 f[i] = 1; 20 } 21 sum += f[i]; 22 if (i - K + 1 >= 0) sum -= f[i - K + 1]; 23 } 24 for (int i = N - K + 1 + 1; i < N; i++) { 25 if (CanFlip(i, sum)) return INF; //无解 26 if (i - K + 1 >= 0) sum -= f[i - K + 1]; 27 } 28 return res; 29 } 30 31 int main() { 32 #ifndef ONLINE_JUDGE 33 freopen("input.txt", "r", stdin); 34 #endif 35 for (int i = 0; i < 20; i++) { 36 scanf("%d", &dir[i]); 37 } 38 int ans1 = solve(); 39 reverse(dir, dir + 20); 40 int ans2 = solve(); 41 // cout << ans1 << " " << ans2 << endl; 42 printf("%d", min(ans1, ans2)); 43 return 0; 44 }
POJ1222
题意:给定一个5行6列的棋盘,每次反转会将这个棋子的上下左右包括他自己都会反转,求最后的反转方案
解法:其实再反转第一行之后,其他行怎么翻就已经确定了,所以我们枚举第一行的状态,之后验证最终局面存不存在未翻的棋子即可
1 int g[MAXN][MAXN]; 2 int tmp[MAXN][MAXN]; 3 int ans[MAXN][MAXN]; 4 int first_line[MAXN]; 5 bool flag; 6 7 void flip(int i, int j) { 8 tmp[i][j] = !tmp[i][j]; 9 tmp[i - 1][j] = !tmp[i - 1][j]; 10 tmp[i + 1][j] = !tmp[i + 1][j]; 11 tmp[i][j - 1] = !tmp[i][j - 1]; 12 tmp[i][j + 1] = !tmp[i][j + 1]; 13 return; 14 } 15 16 bool check() { 17 rep(i, 1, 5) rep(j, 1, 6) if (tmp[i][j]) return 0; 18 return 1; 19 } 20 21 void dfs(int idx) { 22 if (flag) return; 23 24 if (idx == 7) { 25 MEM(ans, 0); 26 memcpy(tmp, g, sizeof(g)); 27 // flip first line 28 rep(j, 1, 6) if (first_line[j]) { 29 flip(1, j); 30 ans[1][j] = 1; 31 } 32 // flip other lines 33 rep(i, 2, 5) rep(j, 1, 6) if (tmp[i - 1][j]) { 34 flip(i, j); 35 ans[i][j] = 1; 36 } 37 38 if (check()) { 39 flag = 1; 40 } 41 42 return; 43 } 44 45 for (int i = 0; i <= 1; i++) { 46 first_line[idx] = i; 47 dfs(idx + 1); 48 if (flag) return; 49 } 50 51 return; 52 } 53 54 int main() { 55 #ifndef ONLINE_JUDGE 56 freopen("input.txt", "r", stdin); 57 #endif 58 int T = READ(); 59 for (int t = 1; t <= T; t++) { 60 rep(i, 1, 5) rep(j, 1, 6) g[i][j] = READ(); 61 flag = false; 62 dfs(1); 63 64 printf("PUZZLE #%d ", t); 65 for (int i = 1; i <= 5; i++) { 66 for (int j = 1; j <= 6; j++) { 67 if (j == 1) 68 printf("%d", ans[i][j]); 69 else 70 printf(" %d", ans[i][j]); 71 } 72 printf(" "); 73 } 74 } 75 return 0; 76 }