zoukankan      html  css  js  c++  java
  • 编程之美第1章 游戏之乐

    1. 使CPU占有率画出正弦曲线

    clip_image002

    clip_image004

    linux下的代码:

    #if 0
    /*
     * Q1.1
     */
    int get_tick_count(){
        struct timeval tv;
        gettimeofday(&tv,NULL);
        return tv.tv_sec*1000000+tv.tv_usec;
    }
    const int Interval=300;
    const int COUNT=300;
    const double split=0.01;
    const double PI=3.1415926;
    
    int main(){
        int busySpan[COUNT];
        int idleSpan[COUNT];
        int half=Interval/2;
        int radian=0;
        for(int i=0;i<COUNT;i++){
            busySpan[i]=(int)(half+sin(PI*radian)*half);
            idleSpan[i]=Interval-busySpan[i];
        }
        int j=0;
        int start;
        while(true){
            j=j%COUNT;
            start=get_tick_count();
            while(get_tick_count()-start<=busySpan[j])
                ;
            sleep(idleSpan[j]);
            j++;
        }
    }
    
    #endif

    2. 中国象棋将帅问题

    clip_image006

    有三种方法:

    方法1:

    使用一个字节, 高4位表示A的位置,然后低4位表示B的位置, 然后剩下的就是分别对高4位和低4位进行操作了.

    代码如下, 对位操作都是用宏实现的, 这样就不会占用其他变量了.

    #if 0
    /*
     * Q1.2
     */
    #include <iostream>
    #define OFFSET 4
    //#define FMASK 0xFF
    #define LMASK 0xF0
    #define RMASK 0x0F
    #define LGET(b) ((LMASK&b)>>OFFSET)
    #define RGET(b) (RMASK&b)
    #define RSET(b,n) (b=((LMASK&b)|n))
    #define LSET(b,n) (b=((RMASK&b)|n<<OFFSET))
    int main(){
        unsigned char b;
        int i=0;
        for(LSET(b,1);LGET(b)<=9;LSET(b,LGET(b)+1)){
            for(RSET(b,1);RGET(b)<=9;RSET(b,RGET(b)+1)){
                if(LGET(b)%3==RGET(b)%3)
                    continue;
                cout<<"A:"<<LGET(b)<<' '<<"B:"<<RGET(b)<<endl;
                i++;
            }
        }
        cout<<i<<endl;
    }
    #endif

    方法2:

    使用一个char类型变量, 由于A和B的位置个数都是9个, 所以我们将”AB”想象成一个9进制的二位数, 那么该9进制数最小为0, 最大为80, 我们的工作就是取出该9进制的高位和低位, 然后判断高位和低位是否符合要求.

    代码如下:

    #if 0
    /*
     * Q1.2
     */
    int main(){
        char b=81;
        int i=0;
        while(b--){
            if(b/9%3==b%9%3)
                continue;
            cout<<"A:"<<b/9<<' '<<"B:"<<b%9<<endl;
            i++;
        }
        cout<<i<<endl;
    }
    
    #endif

    方法3:

    方法3和方法1类似, 只是采用了另一种的思考方式, 使用了C/C++的struct的一个特殊用法:位域.

    代码如下:

    #if 0
    /*
     * Q1.2
     */
    struct _p{
        unsigned char a:4;
        unsigned char b:4;
    }p;
    int main(){
        int i=0;
        for(p.a=1;p.a<=9;p.a++)
            for(p.b=1;p.b<=9;p.b++) {
                if(p.a%3==p.b%3)
                    continue;
                cout<<"A:"<<p.a%3<<' '<<"B:"<<p.b%3<<endl;
                i++;
            }
        cout<<i<<endl;
    }
    
    #endif

    3. 烙饼排序

    clip_image008

    基本思路:

    clip_image010

    4. 买书问题

    clip_image012

    分析:

    这个题先考虑贪心算法, 优先考虑最大的折扣, 但是我们可以发现贪心策略会失效的:

    clip_image014

    再考虑动态规划的方法:

    为了更好的使用动态规划的方法, 我们要考虑一下如何表示所出现的中间状态.

    我们使用Xi表示第i(1<=i<=5) 卷购买数量. F(X1,X2,X3,X4,X5)表示所需要的费用. 从题目的要求中我们可以看出, Xi之间的大小关系是无关紧要的, 因为打折的方法只是要求卷不同, 与具体是哪一卷无关. 为了更好的表示, 我们利用(Y1,Y2,Y3,Y4,Y5)且Y1>=Y2>=Y3>=Y4>=Y5来表示(X1,X2,X3,X4,X5)的所有排列.

    clip_image016

    这样可以得到状态转移方程:

    clip_image018

    clip_image020

    5. 快速找出故障机器

    clip_image022

    这儿给出了4种解法:

    方法1:

    最直接的方法就是对这个ID列表进行遍历, 同时记录每个ID出现的次数, 根据最后记录得到的次数很容易找到出现一次的ID. 不论有几台机器死机, 这种情况都可以完成. 这种情况的时间复杂度是O(N), 空间复杂度是O(N)

    方法2:

    由于在这个问题中, 大部分ID出现的次数都是2, 所以这个2我们可以不必存储.

    做法就是: 使用一个变长数组记录ID出现的次数, 如果某个ID出现次数等于2, 则删除这个ID, 则最后剩下的ID就是出现一次的ID. 这个方法最好情况下空间为O(1), 最坏为O(N)

    方法3:

    这个方法的思想是寻找一个函数, 该函数利用所有的ID可以计算出缺失的ID. 这样的一个函数比较典型的是异或运算, 由于异或运算满足结合律和交换律, 所以将所有ID进行异或之后, 如果ID出现两次, 结果就为0, 这样最终的结果就是只出现一次的ID.

    如果只有一台机器死机, 那么该ID就是所求的ID. 如果有两台机器死机, 比如A和B, 如果A=B那么A⊕B=0, 就是说一份数据的两个拷贝都丢失了, 这样我们就没法确认是哪个了. 如果A⊕B不为0, 那么其结果中肯定有一位为1,这样就根据该位是否为1将所有的ID分为两类:一类是该位为1, 一类是该位为0, 这样两类ID分别异或就得到了A和B.

    方法4:

    该方法是寻找所有ID的一个不变量, 这个不变量就是所有ID的和.所以可以预先计算出所有ID的和,然后再计算出当前所有ID和, 两者的差就是出现一次的ID. 如果是有两个ID出现一次的话,则x+y=a. 这样为了求出x和y, 我们还必须找出一个方程. 可以用同样的方式计算x*y=b, 或者x^2+y^2=c,这样都可以求出x和y.

    1.6 饮料供货

    1.7 光影切割问题

    clip_image024

    clip_image026

    clip_image028

    clip_image030

    clip_image032clip_image034

    1.8 电梯调度算法

    clip_image036

    clip_image038

    clip_image040

    代码如下:

    void best_floor(int *presons,int n,int &floor,int& min_floors){
        if(persons==0||n<=0)
            return;
        floor=-1;
        min_floors=0x7fffffff;
        int count=0;
        for(int i=0;i<n;i++){
            for(int j=1;j<i;j++)
                count+=persons[j]*(i-j);
            for(int j=i+1;j<n;j++)
                count+=persons[j]*(j-i);
            if(floor==-1||min_floors>count){
                min_floors=count;
                floor=i;
            }
        }
    }

    clip_image042

    代码如下:

    void best_floor2(int *presons,int n,int &floor,int& min_floors){
        if(persons==0||n<=0)
            return;
        int N1=0,
            N2=persons[0],
            N3=0;
        min_floors=0;
        for(int i=1;i<n;i++){
            N3+=persons[i];
            min_floors+=persons[i]*i;
        }
        for(int i=1;i<n;i++){
            if(N1+N2<N3){
                floor=i;
                min_floors+=(N1+N2-N3);
                N1+=N2;
                N2=persons[i];
                N3-=persons[i];
            }
            else
                break;
        }
    }

    clip_image044

    1.9 高效的安排见面会

    1.11 NIM(1) 一排石头的游戏

    clip_image046

    clip_image048clip_image050clip_image052clip_image054

    1.12 NIM(2)

    clip_image056

    问题:

    有N块石头和两个玩家A和B,玩家A先将石头随机分成若干堆,然后按照BABA...的顺序不断轮流取石头,能将剩下的石头一次取光的玩家获胜,每次取石头时,每个玩家只能从若干堆石头中任选一堆,取这一堆石头中任意数目(大于0)个石头。

    请问:

    玩家A要怎样分配和取石头才能保证自己有把握取胜?

    如果石头的个数N为偶数,A只要将其分为相同的两份,就一定能取胜。

    初始:XOR(M1, M1) == 0

    玩家B:XOR(M1, M2) != 0  (其中一堆的个数减少到M2)

    玩家A:XOR(M2, M2) == 0  (玩家A将另一堆的个数也减少到M2)

    结果:XOR(M2, M2) == 0  (直到结束状态(0, 0))

    如果石头的个数N为奇数,B有必胜的方法。

    初始:XOR(M1, M2, ... , Mn) != 0

    玩家B:XOR(M1, ... , Mi', ... , Mn) == 0 (其中一堆Mi的个数减少到Mi')

    玩家A:XOR(M1, ... , Mj', ... , Mn) != 0

    玩家B:XOR(M1, ... , Mi', ... , Mn) == 0 (其中一堆Mi的个数减少到Mi')

    结果:XOR(M1, ... , Mj' , ... , Mn) == 0 (直到结束状态(0,0))

    这里就有个问题:已知XOR(M1, M2, ... , Mn) != 0,玩家B该改变那个Mi以使得XOR(M1, ... , Mi', ... , Mn) == 0呢?

    对于这个问题的答案,书中并未准确的结论。

    经过本人的分析,所得到的结论如下:

    设k=XOR(M1, M2, ... , Mn),已知k!=0,取一个数Mi,其二进制表达中在k的最高二进制位上的数为1,且这个

    数Mi肯定存在(k的这个最高位在异或运算中肯定来自某一个Mi)。在程序中满足(Mi&k) > (k>>1)条件的数即为Mi。

    简单证明:即假设k的二进制表达是1xx,那么Mi的二进制表达是x...x1xx,这样玩家B将该Mi改成Xi'=XOR(Mi, k)后,

    Mi'的二进制表达是x...x0yy,肯定小于Mi,并且有XOR(M1, ... , Mi', ... , Mn) == 0。

    参考: http://blog.csdn.net/linyunzju/article/details/7661060

    1.13 NIM(3) 游戏

    问题:

    假设有两堆石头,有两个玩家会根据如下的规则轮流取石头:

    每人每次可以从两堆石头中各取出数量相等的石头,或者仅从一堆石头中取出

    任意数量的石头;最后把剩下的石头一次拿光的人获胜。请问在哪些局面(依

    据两堆石头中的石头个数)下,先取石头的玩家有必胜的策略。

    解法:

    类似构造质数的筛选方法,这里我们利用找到的必输局面(后取的玩家有必胜策略)

    来筛去掉能通过一次操作达该必输局面的其它必胜局面(先取的玩家有必胜策略)。

    最后选出的局面都是必输局面。

    构造必胜策略:

    如果一开始的局面就是必输局面,那么可能先取的玩家没有必胜策略(当然如果后取

    的玩家不太聪明,先取的玩家依然有可能能赢)。如果一开始的局面不是必输局面,

    那么先取的玩家一定有必胜策略,且必胜策略就是保证每次都将当前非必输局面转变

    为必输局面(后取的玩家必输)。

    参考:http://blog.csdn.net/linyunzju/article/details/7674596

    1.15 构造数独

    问题:

    构造一个9*9的方格矩阵,玩家要在每个方格中,分别填上1至9的任意一个数字,
    让整个棋盘每一列、每一行以及每一个3*3的小矩阵中的数字都不重复。

    首先我们通过一个深度优先搜索来生成一个可行解,然后随机删除一定数量的数字,
    以生成一个数独。

    参考: http://blog.csdn.net/linyunzju/article/details/7673959

    1.16 24点游戏

    问题:

    给玩家4张牌,每张牌的面值在1-13之间,允许其中有数值相同的牌,采用加、减、乘、除四则运算,允许中间运算存在小数,并且可以使用括号,但每张牌只能用一次。构造表达式,使其结果为24.

    解法:

    传统的枚举解法会产生大量重复的运算,主要有两类重复:运算结果的重复和排列的重复。假设4张牌为3 3 8 8,我们对3 3进行一次操作(6种运算)得到6 0 0 1 1 9,其中重复的数据就是我们所说的运算结果重复,使用集合不重复性来解决。枚举算法在枚举时要对牌的顺序进行排列,由于牌可以重复,所以产生的排列会有大量的重复(3 3) 8 8, (3 8) 3 8, (3 8) 3 8, (3 8) 3 8,(3 8) 3 8, (8 8) 3 3,这属于排列重复,使用分治法加memo来解决。采用二进制数来表达集合和子集的概念,我们可以用一个数来表示子集中拥有哪些元素,再用这个数作为索引来找出该集合运算后产生的结果集。

    1.17 俄罗斯方块

    问题:

    让电脑自动下俄罗斯方块游戏。

    解法:

    对当前的积木块,枚举它旋转后的每一个形状从每一列落下的棋盘,将该棋盘和前一个棋盘进行对比,并打分,最后取得分最高的那个形状和那一列作为电脑的当前操作。(由于程序的输入数据比较多,我将其和代码打包放在资源下载中,需要的读者可以去下载http://download.csdn.net/detail/linyunzju/4389102,鉴于有读者不会用这个程序,在此说明下,运行这个程序需要加上文件重定向<1_17.txt,或者把程序中的控制台输入scanf改成文件输入FILE* fin=fopen("1_17.txt"); fscanf(fin,"%d",...);)

    代码:

    参考:

    http://blog.csdn.net/linyunzju/article/details/7686822

  • 相关阅读:
    Python挂载杂记
    Python Log的使用, 模块化
    Python实现图片长宽比例缩放和填充
    系统监控与硬盘
    linux基础三
    linux基础二
    linux基础一
    网络基础
    操作系统
    计算机硬件基础
  • 原文地址:https://www.cnblogs.com/xkfz007/p/2758267.html
Copyright © 2011-2022 走看看