《挑战程序设计竞赛》之“反转”问题总结

萌新又来写总结了
“反转”问题在《挑战程序设计竞赛》P150左右的位置~

拒!绝!搜!索!

这类问题有以下几个特征:
1.大多集中在一维/二维两种情形,二维数据范围一般很小(毕竟需要一部分的枚举);是对一个区间/相邻几个格子进行反转;
2.一般用搜索(dfs/bfs)是无法在规定时间内完成的(除非数据太小);
3.每一个格子有两个状态(正/反),区间的反转顺序对最终的结果毫无影响;
4.对某一个格子进行两次以上的反转是多于的,所以只需要%2便可知道反转的结果。

借用一句话:
“反转法跟尺取法有点像,都是限定一个区间,往前挪动,尾部挪动之后,需要减掉尾部的影响。”

这里列举几个经典题目:
一维反转:
Face The Right Way POJ - 3276
The Water Bowls POJ - 3185
二维反转:
Fliptile POJ - 3279
The Pilots Brothers’ refrigerator POJ - 2965
Flip Game POJ - 1753
EXTENDED LIGHTS OUT POJ - 1222

A. Face The Right Way POJ - 3276

tips:我们可以认为,这“反转连续的K头牛”只能按照序号递增的顺序(比如k = 3 ,反转 7 6 8三头连续的牛,可以认为是以6号为起点,连续反转6 7 8 三头牛,顺序毫无影响)。即:问题被转化为求需要反转的区间的集合。

对于最后不足k头牛,直接进行判断,无需再反转(已经不够k头牛了)。

#include<cstdio>
#include<cstring>const int maxn = 5005;
int book[maxn],flip[maxn],n,k,m,ans,reck;
char c;int solve(int k){memset(flip,0,sizeof(flip));int sum = 0, res = 0;         //res用来统计答案(反转次数)for(int i = 1; i <= n - k + 1; ++i){if((book[i] + sum) % 2 == 0)  flip[i] = 0;else   sum++, res++, flip[i] = 1;if(i - k >= 0)   sum -= flip[i - k + 1];   //为下一个位置做准备}for(int i = n - k + 2; i <= n; ++i){        //直接判断if((book[i] + sum) % 2 != 0)    return -1;if(i - k >= 0)    sum -= flip[i - k + 1];}return res;
}int main(){scanf("%d",&n); ans = 1e9;for(int i = 1; i <= n; ++i){getchar();scanf("%c",&c);book[i] = (c == 'B' ? 1 : 0);}for(k = 1; k <= n; ++k){int tmp = solve(k); if(tmp != -1 && ans > tmp){ans = tmp;reck = k;}}printf("%d %d\n",reck,ans);return 0;
}

B.The Water Bowls POJ - 3185

tips:和上一个题相似,只是限定了每次反转自己和相邻2个格子。可以直接运算,不用像上一题那样用sum记录。【唯一的区别在于,如果是两头的格子,只能反转2个;中间的格子可以反转3个,k不是固定值
注意分类讨论:第一个格子不反转/第一个格子反转。
我们认为,从最左端开始,当某一个格子定下来之后,只有后一个格子能改变他(因为他前面的和他已经定下来了),因此根据当前格子的状态可以唯一确定下一个格子的反转次数。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;int book[25],flip[25],minn,tmp[25];void solve1(){       //第一个格子不反转int cnt = 0;memcpy(tmp, book, sizeof(book));memset(flip, 0,sizeof(flip));for(int i = 2; i <= 20; ++i){if((flip[i - 1] + tmp[i - 1]) % 2 == 0)  continue;   //当前格子反转与否,取决于前一个格子的状态else   flip[i]++, tmp[i + 1]++, cnt++;  //当前格子反转,下一个格子的 状态也会受影响,用tmp++表示。之所以用memcpy,是不想改变原来的数据}if((flip[20] + tmp[20]) % 2 == 0)minn = min(minn, cnt);
}void solve2(){     //第一个格子反转int cnt = 0;memset(flip, 0,sizeof(flip));memcpy(tmp, book, sizeof(book));flip[1] = 1, tmp[2]++, cnt++;for(int i = 2; i <= 20; ++i){if((flip[i - 1] + tmp[i - 1]) % 2 == 0) continue;else   flip[i]++, tmp[i + 1]++, cnt++;}if((flip[20] + tmp[20]) % 2 == 0)minn = min(minn, cnt);
}int main(){minn = 1e5;for(int i = 1; i <= 20; ++i)scanf("%d",&book[i]);solve1(); solve2();printf("%d",minn);return 0;
} /*
0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
2
*/

也可以借鉴A题的写法,两次处理(第一个元素改/不改)。注意修改最后一个数据时,只能修改2个,所以判断只是对最后一个进行判断,而不是从第n-k+2个。
另一种写法:

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;const int maxn = 25;
int book[maxn],filp[maxn],n,k,minn;int solve(int k){memset(filp,0,sizeof(filp));int sum = 0, res = 0;for(int i = 1; i <= n - 1; ++i){if((book[i] + sum) % 2 == 0)   filp[i] = 0;else   sum++, res++, filp[i] = 1;if(i - k >= 0)   sum -= filp[i - k + 1];}return (book[n] + sum) % 2 != 0 ? -1 : res;
}int main(){n = 20;for(int i = 1; i <= n; ++i)scanf("%d",&book[i]);int tmp1 = solve(3);      //k = 3, 第一个不反转book[1]++, book[2]++;        //第一个反转相当于次数+1,第1、2个数据+1int tmp2 = solve(3) + 1;printf("%d",min(tmp1,tmp2));   return 0;
}

C.Fliptile POJ - 3279

非常经典的二维反转。因为n,m最大可以到15,用dfs/bfs必会TLE。
每一个格子可以改变自己和上下左右,所以无法向一维反转那样做(因为能改变(1,1)的还可以有(1,2)和(2,1),而上一个题让最左端牛改变的方法只有一种,因为只有一个区间包含1)。

我们可以枚举第一行的所有反转情况,第一行的反转情况定下后,只有其下一行可以改变上一行的状态,以此类推,直到最后一行,然后检查最后一行是否都为0即可。

tips:状态压缩,用二进制表示集合的枚举。

#include<cstdio>
#include<cstring>
using namespace std;const int maxn = 20;
int n,m,book[maxn][maxn],flip[maxn][maxn],ans[maxn][maxn];
int dir[5][2] = {{1,0},{-1,0},{0,0},{0,1},{0,-1}};int getcolor(int x, int y){int c = book[x][y];for(int k = 0; k < 5; ++k){int tx = x + dir[k][0], ty = y + dir[k][1];if(tx < 1 || ty < 1 || tx > m || ty > n)  continue;c += flip[tx][ty];}return c % 2;
}int calc(){for(int i = 2; i <= m; ++i)for(int j = 1; j <= n; ++j)if(getcolor(i - 1, j))  flip[i][j] = 1;for(int j = 1; j <= n; ++j)if(getcolor(m, j))    return -1;int cnt = 0;for(int i = 1; i <= m; ++i)for(int j = 1; j <= n; ++j)cnt += flip[i][j];return cnt;
}void solve(){int res = -1;for(int s = 0; s < 1 << n; ++s){memset(flip, 0, sizeof(flip));for(int j = 1; j <= n; ++j)flip[1][j] = s >> (j - 1) & 1;int num = calc();if(num >= 0 && (res < 0 || res > num)){res = num;memcpy(ans, flip, sizeof(flip));}}if(res < 0) printf("IMPOSSIBLE\n");else{for(int i = 1; i <= m; ++i)for(int j = 1; j <= n; ++j)printf("%d%c",ans[i][j], j == n ? '\n' : ' ');}
}int main(){scanf("%d%d",&m,&n);for(int i = 1; i <= m; ++i)for(int j = 1; j <= n; ++j)scanf("%d",&book[i][j]);solve();return 0;
}

D.Flip Game POJ - 1753
tips:差不多的题,只是n = m = 4,数据范围大幅度缩小,且“翻成同一个面”可以都是正,也可以都是反,分两类讨论即可。
数据范围太小导致dfs也可以过,但是还是反转更优一些。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;int book[10][10],flip[10][10];
int dir[5][2] = {{1,0},{-1,0},{0,0},{0,1},{0,-1}};
char c;int getcolor(int x ,int y){int c = book[x][y];for(int k = 0; k < 5; ++k){int tx = x + dir[k][0], ty = y + dir[k][1];if(tx < 1 || ty < 1 || tx > 4 || ty > 4)  continue;c += flip[tx][ty];}return c % 2;
}int calc1(){for(int i = 2; i <= 4; ++i)for(int j = 1; j <= 4; ++j)if(getcolor(i - 1, j)) flip[i][j] = 1;for(int j = 1; j <= 4; ++j)if(getcolor(4, j))    return -1;int cnt = 0;for(int i = 1; i <= 4; ++i)for(int j = 1; j <= 4; ++j)cnt += flip[i][j];return cnt;
}int calc2(){for(int i = 2; i <= 4; ++i)for(int j = 1; j <= 4; ++j)if(getcolor(i - 1, j) == 0)  flip[i][j] = 1;for(int j = 1; j <= 4; ++j)if(getcolor(4, j) == 0) return -1;int cnt = 0;for(int i = 1; i <= 4; ++i)for(int j = 1; j <= 4; ++j)cnt += flip[i][j];return cnt;
}void solve(){int res1 = -1, res2 = -1;for(int s = 0; s < 1 << 4; ++s){memset(flip, 0, sizeof(flip));for(int j = 1; j <= 4; ++j)flip[1][j] = s >> (j - 1) & 1;int num1 = calc1();if(num1 >= 0 && (res1 == -1 || res1 > num1)) res1 = num1;}for(int s = 0; s < 1 << 4; ++s){memset(flip, 0, sizeof(flip));for(int j = 1; j <= 4; ++j)flip[1][j] = s >> (j - 1) & 1;int num2 = calc2();if(num2 >= 0 && (res2 == -1 || res2 > num2))    res2 = num2;}if(res1 == -1 && res2 == -1)  printf("Impossible\n");else if(res1 == -1)  printf("%d",res2);else if(res2 == -1)   printf("%d",res1);else    printf("%d",min(res2, res1));
} int main(){for(int i = 1; i <= 4; ++i){for(int j = 1; j <= 4; ++j){scanf("%c",&c);book[i][j] = (c == 'b') ? 1 : 0;       //b是1}getchar();}solve();return 0;
}

E.EXTENDED LIGHTS OUT POJ - 1222
tips:同3279.多组输入,且固定m = 5, n = 6。

#include<cstdio>
#include<cstring>
using namespace std;const int maxn = 10;
int t,n,m,book[maxn][maxn],flip[maxn][maxn],ans[maxn][maxn];
int dir[5][2] = {{1,0},{-1,0},{0,0},{0,1},{0,-1}};int getcolor(int x, int y){int c = book[x][y];for(int k = 0; k < 5; ++k){int tx = x + dir[k][0], ty = y + dir[k][1];if(tx < 1 || ty < 1 || tx > m || ty > n)  continue;c += flip[tx][ty];}return c % 2;
}int calc(){for(int i = 2; i <= m; ++i)for(int j = 1; j <= n; ++j)if(getcolor(i - 1, j))  flip[i][j] = 1;for(int j = 1; j <= n; ++j)if(getcolor(m, j))    return -1;int cnt = 0;for(int i = 1; i <= m; ++i)for(int j = 1; j <= n; ++j)cnt += flip[i][j];return cnt;
}void solve(){int res = -1;for(int s = 0; s < 1 << n; ++s){memset(flip, 0, sizeof(flip));for(int j = 1; j <= n; ++j)flip[1][j] = s >> (j - 1) & 1;int num = calc();if(num >= 0 && (res < 0 || res > num)){res = num;memcpy(ans, flip, sizeof(flip));}}if(res < 0) printf("IMPOSSIBLE\n");else{for(int i = 1; i <= m; ++i)for(int j = 1; j <= n; ++j)printf("%d%c",ans[i][j], j == n ? '\n' : ' ');}
}int main(){scanf("%d",&t);for(int x = 1; x <= t; ++x){m = 5, n = 6;for(int i = 1; i <= m; ++i)for(int j = 1; j <= n; ++j)scanf("%d",&book[i][j]);printf("PUZZLE #%d\n",x);solve();}return 0;
}

F.The Pilots Brothers’ refrigerator POJ - 2965

tips:有点变化。以上的二维反转都是只改变自己和上下左右,这个题除了改变自己,还会改变所在行、所在列的所有格子。固定大小4*4.

一个重要的发现:如第一行第二个是+,那个只要他所在的行和列的格子全都翻转一次,就会让当前的 + 变成 - ,但是其他格子全部都不会发生变化。(证明:某一个格子所在行/列一共加起来的反转次数如果是偶数,该格子不变;奇数,该格子反转。上述操作后,整个棋盘除了该格子反转次数是奇数(4 + 3 == 7),其余位置均为偶数。可以动手算一下)
于是,发现+格子,就把他所在行列所有格子的flip++,最后答案就是所有格子的(flip%2)相加。

4*4用dfs也能过,但是用反转的思想显然比dfs快很多。

#include<cstdio>
#include<utility>
#include<vector>
using namespace std;int book[5][5], flip[5][5],ans;
char c;
vector<pair<int,int> > v;
vector<pair<int,int> > :: iterator it;void change(int x, int y){for(int i = 1; i <= 4; ++i)  flip[x][i]++;for(int i = 1; i <= 4; ++i)   flip[i][y]++;flip[x][y]--;
}int main(){for(int i = 1; i <= 4; ++i){for(int j = 1; j <= 4; ++j)scanf("%c",&c), book[i][j] = (c == '+') ? 1 : 0;getchar();}for(int i = 1; i <= 4; ++i){for(int j = 1; j <= 4; ++j){if(book[i][j])    change(i, j);}}for(int i = 1; i <= 4; ++i)for(int j = 1; j <= 4; ++j){ans += flip[i][j] % 2;if(flip[i][j] % 2)  v.push_back(make_pair(i, j)); }printf("%d\n",ans);for(it = v.begin(); it != v.end(); ++it)printf("%d %d\n",it->first, it->second);return 0;
}

《挑战程序设计竞赛》之“反转”问题总结相关推荐

  1. 《挑战程序设计竞赛(第2版)》习题册攻略

    本项目来源于GitHub 链接: 项目GitHub链接 1 前言 项目为<挑战程序设计竞赛(第2版)>习题册攻略,已完结.可配合书籍或笔记,系统学习算法. 题量:约200道,代码注释内含详 ...

  2. 挑战程序设计竞赛(第2版)》

    <挑战程序设计竞赛(第2版)> 基本信息 作者: (日)秋叶拓哉 岩田阳一 北川宜稔 译者: 巫泽俊 庄俊元 李津羽 丛书名: 图灵程序设计丛书 出版社:人民邮电出版社 ISBN:9787 ...

  3. 挑战程序设计竞赛:反转法

    挑战程序设计竞赛:反转法 1. 题目介绍 1.1 题目 1.2 样例 2. 思路讲解 2.1 视频讲解 2.1 反转法(开关问题) 3. 练习题 4. 附录:程序代码 4.1 Java 4.2 C++ ...

  4. POJ 1150 The Last Non-zero Digit 《挑战程序设计竞赛》

    为什么80%的码农都做不了架构师?>>>    POJ 1150 The Last Non-zero Digit超大组合数:求超大组合数P(n, m)的最后一个非零位.4.1更加复杂 ...

  5. POJ 3735 Training little cats​ 题解 《挑战程序设计竞赛》

    为什么80%的码农都做不了架构师?>>>    POJ 3735 Training little cats调教猫咪:有n只饥渴的猫咪,现有一组羞耻Play,由k个操作组成,全部选自: ...

  6. POJ 3608 Bridge Across Islands 《挑战程序设计竞赛》

    为什么80%的码农都做不了架构师?>>>    POJ 3608 Bridge Across Islands跨岛大桥:在两个凸包小岛之间造桥,求最小距离?3.6与平面和空间打交道的计 ...

  7. AOJ 1312 Where's Wally 题解《挑战程序设计竞赛》

    为什么80%的码农都做不了架构师?>>>    本文由码农场 同步,最新版本请查看原文:http://www.hankcs.com/program/algorithm/aoj-131 ...

  8. ICPC程序设计题解书籍系列之三:秋田拓哉:《挑战程序设计竞赛》(第2版)

    白书<挑战程序设计竞赛>(第2版)题目一览 白书:秋田拓哉:<挑战程序设计竞赛>(第2版) 第1章 蓄势待发--准备篇(例题) POJ1852 UVa10714 ZOJ2376 ...

  9. 挑战程序设计竞赛——详解DFS及BFS

    挑战程序设计竞赛--详解DFS及BFS 一.学会要用到的stl函数,Stack.Quene.Pair 1.Stack(DFS隐式的用到,并与Queue对比记忆) 头文件==#include== sta ...

最新文章

  1. 微信授权获取用户的openid和支付宝授权获取用户的userid
  2. HTTPS协议之SSL/TLS协议四次握手
  3. ts 函数声明及泛型函数
  4. 如何用express+node+ejs 搭建一个简单的页面
  5. eclipse中使用git回到之前的版本
  6. HBase之Rowkey设计总结与实战篇
  7. mybatis连接mysql url_MyBatis与JDBC连接数据库所使用的url之间的差异
  8. 196.删除重复的电子邮箱
  9. 福州计算机专业的大学的校徽,如此好看的大学LOGO,有你的母校吗?
  10. JS格式化字符串(两种方法)
  11. Promise 是什么?
  12. 哪些情况会造成小程序违规或下架
  13. 一年有50万主播入驻淘宝,宇宙的尽头是编制,直播的尽头是淘宝?
  14. C++11智能指针(unique_ptr、shared_ptr、weak_ptr)boost::scoped_ptr
  15. 【必须知道的职场情商训练7法】
  16. 添加Adobe PDF 打印机
  17. 转换成BCNF的无损连接分解
  18. 跟我学spring security系列文章第一章 实现一个基本的登入
  19. SOJ 4583 动态规划之分组背包
  20. 宇视摄像机码流类型定码率和变码率的区别?

热门文章

  1. 建筑施工技术【13】
  2. 启动http-server html页面和css无法读取
  3. 【PHPWord】基于Word模板替换生成输出表格动态生成内容、合并单元格、设置单元格背景颜色
  4. PHP怎样将数字值转化为字母,php如何把数字转成大写字母
  5. 【玩转python】python实现不同温度之间的互相转换(附源码)
  6. 业务数据相同,用友U8与金蝶K3计算的实际成本相同吗
  7. 黑客自助餐,“生鱼片”盛宴
  8. ProcessOn一款非常不错在线绘图工具
  9. 04.iOS 使用lame将wav转换为mp3
  10. listView点击置顶操作