开始学习ACM的习题了,从简单题开始。而事实证明,简单题真的不简单,至少对我来说,不简单。
POJ_1753这道题给我的感觉跟亚洲预选赛成都赛区的热身赛开灯关灯的题特别相似。看到这道题就后悔为什么当时没做这道题呢!
后悔完了还是回到现实,学习别人是怎么做的吧!题目我就不解释了,不会的自己找中文的翻译,百度上面可以找到的。
先把代码贴出来吧
- package poj.exercise_1753_AC;
- import java.util.Scanner;
- /*这个题目在枚举上也有一点小的技巧。如果要是暴力枚举的话,
- * 一旦棋盘到了16*16的话显然就吃不消了(一个东欧区域赛的题目)
- * ,实际上我们完全可以只枚举第一行的操作,之后,如果我们想把棋子
- * 全部翻成一种颜色的话,那么第二行的操作就是固定的了(因为第一行的
- * 棋子的状态对第二行棋子的翻转进行了约束,如果想把第一行的棋子变成
- * 白色,那么第二行中位于第一行黑色棋子下方的位置必须翻转,反之亦然),
- * 那么第三行、第四行的操作显然也是固定的了。
- */
- public class Main {
- static int steps=0x7fffffff;
- //(x,y)坐标合起来就是中心点及上下左右坐标啦!
- static int[] dx={0,0,0,1,-1};
- static int[] dy={0,1,-1,0,0};
- /*
- * 把st以2进制表示,每四个的排,排四列,从右下为(0,0)左上为(3,3)
- * @param x竖坐标点
- * @param y横坐标点
- * @param st需要处理的数
- * @return 改变确定位置的状态,如1变成0或者0变成1
- * */
- public static int flip(int x, int y, int source){
- if(x >= 0 && x < 4 && y >= 0 && y < 4)
- source ^= 1 << (x * 4 + y);
- return source;
- }
- /*
- * @param current当前行
- * @param num 回合数
- * @param source 原数据
- * @param flag 标志 如果数据源当前位的状态不为flag,则翻动
- * */
- public static void dfs(int current,int num,int source,int flag){
- //如果最后一行已经翻完
- if(current==4){
- if(source==0xffff||source==0){
- //已经完成了任务
- steps=num<steps?num:steps;
- }
- return;
- }
- //把当前行都翻成同种颜色
- int x,y;
- for (int i = current-1,j=0; j < 4; j++) {//每行有四个,所以需要翻四次
- if( (((source& (1<< (i*4+j) ))>>(i*4+j)) ^ flag)==1 ){
- /*source& (1<< (i*4+j) )>>(i*4+j) :把source中的(i,j)的状态取出来*/
- for (int k = 0; k <5; k++) {//当前,上下左右都得翻动
- x=current+dx[k];
- y=j+dy[k];
- source=flip(x, y, source);
- }
- num++;
- }
- }
- //翻下一行
- dfs(current+1, num, source, flag);
- }
- /*
- * */
- public static int solve(int source){
- for (int i = 0; i < 16; i++) {
- int num=0,temp=source,x,y;
- for (int j = 0; j < 4; j++) {
- if((i&(1<<j))>0){
- for (int k = 0; k <5; k++) {//当前,上下左右都得翻动
- x=0+dx[k];
- y=j+dy[k];
- temp=flip(x, y, temp);
- }
- num++;
- }
- }
- dfs(1, num, temp, 0);
- dfs(1, num, temp, 1);
- }
- return steps==0x7fffffff?-1:steps;
- }
- public static void main(String[] args) {
- Scanner scanner=new Scanner(System.in);
- int source=0;
- String string="";
- for (int i = 0; i < 4; i++) {
- string+=scanner.nextLine().trim();
- }
- for (int i = 0; i < string.length(); i++) {
- source=(source<<1)+(string.substring(i, i+1).equals("b")?1:0);
- }
- if(solve(source)!=-1){
- System.out.println(steps);
- }else {
- System.out.println("Impossible");
- }
- }
- }
申明一下,这不是我自己想出来的,这段代码的C语言代码来自于:http://www.cnblogs.com/staginner/archive/2011/10/29/2228784.html
我只能说一下我对这段代码的理解了:首先,它充分运用了位运算。如果没有学好位运算的同学,这道题目真的是绝佳的学习学习机会。
其具体实现思想我觉得是这样的:首先把字符转化成int ,而且以二进制的方式操作。希望能注意:以二进制的方式操作。
看到程序开始我都不明白dx,dy是做什么用的?原来它是这样工作的:
把字符转化成相对应的0,1数字。以四个为一组存入int型内。dx,dy就是需要改变的点的上、下、左、右点的相对位置。设需要改变点的坐标为(0,0)。这样就好理解了!第二点就是solve方法中的循环16次,开始我也看不明白,后来才发现0-15变成2进制数,分别与1,10,100,1000(if(i&(1<<j))相与,则正好是0000,0001……1111,16种情况,不重不漏。这样就把一行的所有可能都枚举出来了!唉,不得不佩服这样的想法啊!