1. 使CPU占有率画出正弦曲线
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. 中国象棋将帅问题
有三种方法:
方法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. 烙饼排序
基本思路:
4. 买书问题
分析:
这个题先考虑贪心算法, 优先考虑最大的折扣, 但是我们可以发现贪心策略会失效的:
再考虑动态规划的方法:
为了更好的使用动态规划的方法, 我们要考虑一下如何表示所出现的中间状态.
我们使用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)的所有排列.
这样可以得到状态转移方程:
5. 快速找出故障机器
这儿给出了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 光影切割问题
1.8 电梯调度算法
代码如下:
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; } } }
代码如下:
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; } }
1.9 高效的安排见面会
1.11 NIM(1) 一排石头的游戏
1.12 NIM(2)
问题:
有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",...);)
代码:
参考: