一、枚举:
枚举重点关注三个方面:顺序(循环的内外关系、以及枚举时的大小顺序)、范围(循环的范围,往往是作优化用)、变量的变化方式(从大到小&从小到大、※以及一次变化多少)
例1,对于这个题目,我们应该确定的事情,一个是多重循环中谁在内谁在外,再一个是循环的范围,还有就是循环变量是从大到小还是从小到大。
1.循环的内外关系:a、b、c、d依次由外到内--这样可以依次保证a最小、a相等时b最小、ab相等时c最小、abc相等时找到的d最小
2.循环的范围---往往题目中会给出各个变量之间的制约关系,这也是优化枚举策略的重点
※3.循环变量的增减方式--这一条实际上很容易忽略。比如这个题目,给了四种“值小则优先输出”的条件,但是很容易发现,只需要枚举的时候从小到大枚举,并且设计好循环的内外关系(最外层的是最需要“先”保证最小的),就可以求得这样的条件下的结果
例2,一个非常典型的通过“跳着试”来进行优化的题目--如果只是看两个,比如体力和情商,可以一次跳23天(体力的周期),这样就可以首先满足位于体力高峰,然后再看是否同为情商高峰。三个也是同理,可以一次跳过体力和情商(23和28的最小公倍数),来看是否同为智商的高峰。
例3,也蕴含着枚举题目中的一个很重要的思想----先假设再验证。
对于不同的假设进行验证即可得出结论
例4,熄灯问题。这种题目,往往会可能具有两种性质中的一种——一种是类似于“最优子结构”的性质--当前行的状态仅由上一行的灯的状态决定。仅由上一行决定也就说明了,后续子问题的状态不会再返回来影响它的状态。当然这个状态,指的是题目需要的状态,比如全灭,而不仅仅指的是特定灯的开或关;另一种就是此题这样的更加简单的子结构,第一行只要决定了,其实所有的都决定了。
①和②的分析非常关键--否则还会引出对于按钮按下的次数&按下的顺序的讨论
解题时往往会需要这种思想--把结论中的一条提出来当作条件,看这个条件成立时另外一个结论是否可以成立,其实有一点点像离散数学里的CP规则(也不怎么符合,因为CP需要的是蕴含或者析取式)。
这个题除了选用位运算外还有一个优化点--一共五行六列,如果枚举第一行的状态,就是2^6=64种,但是同样的,也可以枚举第一列的状态,就变成了2^5=32种
由此题引出的位运算的一些技巧:
1.封装--SetBit,GetBit,FlipBit
①SetBit:把某个数的右数第某位修改成指定的0/1:
void SetBit(int& val,int pos,bool num){
//参数分别为:要修改的数,右数第几位(注意最右边是第0位!),修改成0/1
if(num){
val|=(1<<pos);
return;
}
val&=(~(1<<pos));
}
②GetBit:Getter类函数推荐写作const,让人一看就知道是一个只读的函数
同样的,位置也是从右边开始,下标从0开始
bool GetBit(const int val, int pos)
{
return val & (1 << pos);
}
③FlipBit:
翻转某一位--当第二个参数(pos)没有传入的时候,把整个数翻转。所以第二个参数的默认值应该是个特殊值,比如-1
????
另外:像这些短小但可能经常使用的函数,为了保证效率,可以均设置为inline函数
其他的一些位运算的典型题,在博客的其他文章里有整理。