[ACM训练] 算法初级 之 基本算法 之 枚举(POJ 1753+2965)
先列出题目:
1、POJ 1753
POJ 1753 Flip Game:http://poj.org/problem?id=1753
Sample Input
bwwb
bbwb
bwwb
bwww
Sample Output
4
入手竟然没有思路,感觉有很多很多种情况需要考虑,也只能使用枚举方法才能解决了吧~
4x4的数组来进行数据存储的话操作起来肯定非常不方便,这里借用位压缩的方法来存储状态,使用移位来标识每一个位置的的上下左右的位置操作。 详细看这里。
1、当棋盘状态id为0(全白)或65535(全黑)时,游戏结束,0^1=1,1^1=0,所以翻转的操作可以通过异或操作来完成,而翻转的位置可以通过移位来确定。
2、结束标识!!!!!
分步骤:
1、从输入到位标识状态:
int state = ;
char ch[]; while(cin>>ch)
{
for(int j = ; j < ; j++)
{
state = state<<;
if(ch[j] == 'b')
state += ;
}
}
2、从一个状态到下一个状态的的转换,比如对应位置i处进行改变后得到的状态:
这里的16个数据使用代码生成可能是想不到的,但是可以通过手动变换一次获得~~~
其实相当于
0 0 0 0
0 0 0 0
0 0 0 0
0 0 0 0
在每一个位置进行变换后的状态记录,正好对应疑惑的16个数据
int change[] ={
,,,,
,,,,
,,,,
,,,
}; state = state^change[i];//对应第i个位置的改变 //上面的16个数据是由下面的代码得来的 int dir[][]={{,},{-,},{,},{,-}};
void init()
{
int i,j,x,y,t,temp;
for(i=;i<;++i)
{
for(j=;j<;++j)
{
temp = ;
temp ^= (<<((-i)*+-j)); //第一行代表16位的高4位,同理第一列也代表高位,所以棋盘(i,j)处在16位中的位置是((3-i)*4+3-j) for(t=;t<;++t)
{
x = i + dir[t][];
y = j + dir[t][];
if(x< || y< || x> || y>)
continue;
temp ^= (<<((-x)*+-y));
}
cout<<temp<<" ";
}
cout<<endl;
}
}
3、判断是否满足情况只需要判断state == 0 || state == 65535 即可。
4、解决了小问题,再思考一下大逻辑:
初始状态即为纯色则直接输出0,初始不是纯色,翻转一个的状态是纯色,则输出1,翻转一个会产生n种状态,0<=n<=16,这些状态要放入到队列中进行保存,再从中出队列再进行下一次的翻转。关键问题是什么情况下判定翻转结束,仍然没有纯色出现,则输出impossible,大于2次的翻转的初态都是从队列中取出来的,必须提前设计一个状态标识,表明同一种状态不再次进行入队列,那么当队列为空时才可以下结论。
//需要设置一个是否处理过的标识,共有状态有65536个,即0-65535,
//如果对应位置被标记为1了,则说明已经处理过,直接抛弃进行下一次 //这样的遍历过程被称为BFS的过程 bool visit[]; int bfs(int state)//返回值是进行的步数,impossible为-1
{
queue<Node> q;
Node current,next; current.state = state;
current.step = ;
q.push(current);
visited[state] = true; while(!q.empty())
{
current = q.front();
q.pop(); if(current.state == || current.state == )
return current.step; for(int i = ;i<;i++)//每一种状态都要进行16次操作
{
next.state = current.state^change[i];
next.step = current.step+; if(next.state == || next.state == )
return next.step;
else
{
if(visited[next.state])
continue;
else
{
visited[next.state] = true;
q.push(next);
}
}
}
}
return -;
}
总结:枚举,此题就是使用枚举的思想来找到需要的解,尤其是使用队列来进行一个的while循环来进行的。
这里记录一下全部代码:
#include <iostream>
#include <stdio.h>
#include<queue> using namespace std; int change[] ={
,,,,
,,,,
,,,,
,,,
}; struct Node
{
int state;
int step;
};
int state = ;
bool visited[]; int bfs(int state)//返回值是进行的步数,impossible为-1
{
queue<Node> q;
Node current,next; current.state = state;
current.step = ;
q.push(current);
visited[state] = true; while(!q.empty())
{
current = q.front();
q.pop(); if(current.state == || current.state == )
return current.step; for(int i = ;i<;i++)//每一种状态都要进行16次操作的
{
next.state = current.state^change[i];
next.step = current.step+; if(next.state == || next.state == )
return next.step;
else
{
if(visited[next.state])
continue;
else
{
visited[next.state] = true;
q.push(next);
}
}
}
}
return -;
} int main()
{
char ch[];
while(cin>>ch)
{
for(int j = ; j < ; j++)
{
state = state<<;
if(ch[j] == 'b')
state += ;
}
} memset(visited,false,sizeof(visited));
int count = bfs(state);
if(count == -)
cout<<"Impossible";
else
cout<<count;
return ;
}
下一个类似的题目是POJ的2965题目:The Pilots Brothers' refrigerator http://poj.org/problem?id=2965
Sample Input
-+--
----
----
-+--
Sample Output
6
1 1
1 3
1 4
4 1
4 3
4 4
分析:与上面的类似,需要解决的分步骤有如下几个:
1、一次状态改变的对应异或状态,共有16个
2、每一个Node包括状态值、进行的步骤以及达到此状态之前进行的步骤序列
3、成功的标识是全部都变成减号,即冰箱门打开。
分步骤:
1、生成16个异或数据+对应0,-对应1,每次进行同行同列的反转
相当于
0 0 0 0
0 0 0 0
0 0 0 0
0 0 0 0
在每一个位置进行变换后的状态记录,正好对应异或的16个数据
int change[] ={
, , , ,
, , , ,
, , , ,
, , ,
};
2、给出Node的定义,包括三个数据
struct Node
{
int state;
int step;
string seq;//使用string可以append操作步骤,每次添加2个,最后当成数组读出来即可
};
3、这里先使用与上面的方法一致的策略得出结果,可以得出正确结果,但是超时,超时,超时,Time Limit Exceeded!!!
代码在这里,思路与上面的题目一致!
#include <iostream>
#include <stdio.h>
#include<queue>
using namespace std; int change[] ={
, , , ,
, , , ,
, , , ,
, , ,
}; struct Node
{
int state;
int step;
string seq;
};
int state = ;
bool visited[]; string int2str(int num)
{
if(num == )
return "";
string str = "";
int num_ = num > ? num : - * num;
while(num_)
{
str = (char)(num_ % + ) + str;
num_ /= ;
}
if(num < )
str = "-" + str;
return str;
} Node bfs(int state)//返回值是进行的步数,impossible为-1
{
queue<Node> q;
Node current,next; current.state = state;
current.step = ;
current.seq ="Z"; q.push(current);
visited[state] = true; while(!q.empty())
{
current = q.front();
q.pop(); if(current.state == )
return current; for(int i = ;i<;i++)//每一种状态都要进行16次操作的!!!!!
{
next.state = current.state^change[i];
next.step = current.step+;
//这里添加上操作的步骤序列
next.seq = "Z";
string tmpi = int2str(i/ + );
string tmpj = int2str(i% + ); next.seq = current.seq;
next.seq.append(tmpi);
next.seq.append(tmpj); if(next.state == )
return next;
else
{
if(visited[next.state])
continue;
else
{
visited[next.state] = true;
q.push(next);
}
}
}
}
current.step = -;
return current;
} int main()
{
freopen("data.in", "r", stdin); char ch[];
while(cin>>ch)
{
for(int j = ; j < ; j++)
{
state = state<<;
if(ch[j] == '-')
state += ;
}
} Node count = bfs(state);
if(count.step != -)
{
cout<<count.step<<endl;
for(int tt = ;tt<count.seq.length();tt+=)
{
cout<<count.seq[tt]<<" "<<count.seq[tt+]<<endl;
}
} fclose(stdin);
return ;
}
4、需要重新分析题目逻辑,是不是哪里的分析啰嗦了,导致占用了过多的耗时!!!
题目的操作是整行整列翻转,所以按照每一个位置进行操作会产生很多的冗余操作,看这里的分析:(感谢原作者hackbuteer1)
- 先看一个简单的问题,如何把'+'变成'-'而不改变其他位置上的状态?答案是将该位置(i,j)及位置所在的行(i)和列(j)上所有的handle更新一次,
- 结果该位置被更新了7次,相应行(i)和列(j)的handle被更新了6次,剩下的被更新了4次.
- 被更新偶数次的handle不会造成最终状态的改变.因此得出高效解法,在每次输入碰到'+'的时候自增该位置与相应的行和列,当输入结束后,遍历数组,所有为T的位置则是操作的位置, 而T位置的个数之和则是最终的操作次数.
这里做一个理解:就是最终单纯的将一个位置由+变成-,只需要将它本身改变一次即可,其它相当于状态不变。
上面的方法可能不好理解,可以考虑另外一种方法:即使用深度搜索的方法。
代码没能够完全调试通过,先放一下,后面再回来学习。
#include <iostream>
#include <stdio.h>
#include<queue>
using namespace std; int change[] ={
, , , ,
, , , ,
, , , ,
, , ,
}; int state = ;
bool flag=false;
bool visited[]; int step;
int ri[],cj[]; void dfs(int bit, int deep)//深度遍历
{
if(deep==step)
{
flag = (state == );
return;
} if(flag || bit>)
return; ri[deep]=bit/;
cj[deep]=bit%; state = state ^ change[bit];
dfs(bit+,deep+); state = state ^ change[bit];
dfs(bit+,deep); return;
} int main()
{
freopen("data.in", "r", stdin); char ch[];
while(cin>>ch)
{
for(int j = ; j < ; j++)
{
state = state<<;
if(ch[j] == '-')
state += ;
}
} for(step = ;step<=;step++)//共16个操作点,按照每一个操作点进行深度遍历
{
dfs(,);
if(flag)//满足条件直接跳出
break;
} //输出
cout<<step<<endl;
for(int tt = ;tt<step;tt++)
{
cout<<ri[tt]+<<" "<<cj[tt]+<<endl;
} fclose(stdin);
return ;
}
学习总结:针对枚举法的总结
枚举法就是从可能的解中一一进行枚举,并使用给定的条件进行判定,找到满足条件的一个或者全部解即结束。枚举法本质上属于搜索的算法。枚举法的特点是算法简单,对于可确定的解的值域但是又没有很好地算法可以解决时就可以考虑使用枚举法,比较原始,运算量大,算法的时间复杂度是指数级别的,一定注意考虑在小范围内局部中使用枚举法效率会高一些!!!
另外针对广度优先BFS和深度优先DFS两个算法进行学习,后面的搜索算法,图搜索等二叉树搜索等会经常用到,需要深刻学习!!!
[ACM训练] 算法初级 之 基本算法 之 枚举(POJ 1753+2965)的更多相关文章
- 谷歌大规模机器学习:模型训练、特征工程和算法选择 (32PPT下载)
本文转自:http://mp.weixin.qq.com/s/Xe3g2OSkE3BpIC2wdt5J-A 谷歌大规模机器学习:模型训练.特征工程和算法选择 (32PPT下载) 2017-01-26 ...
- 算法<初级> - 第二章 队列、栈、哈希表相关问题
算法 - 第二章 数据结构 题目一 用数组实现大小固定的队列和栈(一面题) 数组实现大小固定栈 /*** * size是对头索引(initSize是固定大小) 也是当前栈大小 * size=下个进队i ...
- 算法<初级> - 第一章 排序相关问题
算法 - 第一章 时间复杂度: Big O 时间/空间复杂度计算一样,都是跟输入数据源的大小有关 n->∞ O(logn) 每次只使用数据源的一半,logn同理 最优解 先满足时间复杂度的情况最 ...
- ACM/ICPC 之 网络流入门-EK算法(参考模板)(POJ1273)
基于残留网络与FF算法的改进-EK算法,核心是将一条边的单向残留容量的减少看做反向残留流量的增加. //网络流 //EK算法 //Time:16Ms Memory:348K #include<i ...
- 【ACM程序设计】求短路 Floyd算法
最短路 floyd算法 floyd是一个基于贪心思维和动态规划思维的计算所有点到所有点的最短距离的算法. P57-图-8.Floyd算法_哔哩哔哩_bilibili 对于每个顶点v,和任一顶点对(i, ...
- 寒假的ACM训练(一)
今天开始ACM训练,选择了刘汝佳的<挑战编程>,暂时算是开始了. 测评的网址: http://www.programming-challenges.com 第一个题目是水题啦.3n+1. ...
- 2014暑假ACM训练总结
2014暑假ACM训练总结报告 匆匆之中,一个暑假又过去了,在学校训练的这段日子真的是感觉日子过得好快啊! 时光如箭,日月如梭! 匆忙的学习之中一个暑假就这样结束了,现在就来写一些总结吧,供自己以后阅 ...
- 决策树-预测隐形眼镜类型 (ID3算法,C4.5算法,CART算法,GINI指数,剪枝,随机森林)
1. 1.问题的引入 2.一个实例 3.基本概念 4.ID3 5.C4.5 6.CART 7.随机森林 2. 我们应该设计什么的算法,使得计算机对贷款申请人员的申请信息自动进行分类,以决定能否贷款? ...
- opencv3中的机器学习算法之:EM算法
不同于其它的机器学习模型,EM算法是一种非监督的学习算法,它的输入数据事先不需要进行标注.相反,该算法从给定的样本集中,能计算出高斯混和参数的最大似然估计.也能得到每个样本对应的标注值,类似于kmea ...
随机推荐
- ARM概论(Advanced RISC Machines)
简介 ARM7是32 位通用微处理器ARM(Advanced RISC Machines)家族中的一员,具有比较低的电源消耗和良好的性价比, 基于(精简指令)RISC结构,指令集和相关的译码机制与微程 ...
- Docker - command in docker container
1.查看Container 里面运行的进程 在运行容器以后,可以查看里面的进程: docker top <container_id> or <container_name> 2 ...
- Redis 3.0 Cluster集群配置
Redis 3.0 Cluster集群配置 安装环境依赖 安装gcc:yum install gcc 安装zlib:yum install zib 安装ruby:yum install ruby 安装 ...
- js最详细的基础,jquery 插件最全的教材
一.Js的this,{},[] this是Javascript语言的一个关键字,随着函数使用场合的不同,this的值会发生变化.但是有一个总的原则,那就是this指的是调用的函数自己. { } 大括号 ...
- javaSE基础05
javaSE基础05:面向对象 一.数组 数组的内存管理 : 一块连续的空间来存储元素. Int [ ] arr = new int[ ]; 创建一个int类型的数组,arr只是一个变量,只是数组的一 ...
- .net学习笔记----HttpRequest,WebRequest,HttpWebRequest区别
WebRequest是一个虚类/基类,HttpWebRequest是WebRequest的具体实现 HttpRequest类的对象用于服务器端,获取客户端传来的请求的信息,包括HTTP报文传送过来的所 ...
- UWP学习记录12-应用到应用的通信
UWP学习记录12-应用到应用的通信 1.应用间通信 “共享”合约是用户可以在应用之间快速交换数据的一种方式. 例如,用户可能希望使用社交网络应用与其好友共享网页,或者将链接保存在笔记应用中以供日后参 ...
- UWP学习记录4-设计和UI之控件和模式1
UWP学习记录4-设计和UI之控件和模式1 1.控件和事件简介 在 UWP 应用开发中,控件是一种显示内容或支持交互的 UI 元素. 控件是用户界面的构建基块. 我们提供了超过 45 种控件供你使用, ...
- CozyRSS开发记录10-RSS源管理
CozyRSS开发记录10-RSS源管理 1.RSS源树结构 做解析体力活很多,把RSS解析的优化先放放,先玩一玩RSS源的管理. 虽然在初步的设计中,RSS源是以一个列表的方式来展示,但是,我觉得如 ...
- Beginning Scala study note(9) Scala and Java Interoperability
1. Translating Java Classes to Scala Classes Example 1: # a class declaration in Java public class B ...