zoukankan      html  css  js  c++  java
  • 开关问题

    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 }
  • 相关阅读:
    四叉树编码存储的实现
    窗体之间传递值的几种方法
    常见的六种排序算法实现
    OracleHelper类
    c#动态加载dll文件
    STL学习系列九:Map和multimap容器
    STL学习系列八:Set和multiset容器
    STL学习系列七:优先级队列priority_queue容器
    STL学习系列六:List容器
    STL学习系列五:Queue容器
  • 原文地址:https://www.cnblogs.com/romaLzhih/p/11620077.html
Copyright © 2011-2022 走看看