暑假总结
上个月末在学java,自己写了个坦克大战,体会了一下面向对象的开发。
也算触类旁通啦~
本来想好好研究一下c++ primer plus,但感觉不如c++ primer实在。
开学了。
翻了翻离散的书,发现太好用了。果然站的高才能看得远啊。
缺少理论知识,所以有时做起题目很困难。
好好学离散,好好学数据结构。
一些零散的学习笔记:
一、
ceil(x) //返回最小大于x的整数
floor(x) //返回最大小于x的整数
应用:
比如四舍五入floor(x+0.5)
二、cstring中的函数
memcpy(b,a,sizeof(a));
memset(a,0,sizeof(a));
请只用0和-1作为memset的参数。如果你的参数是2那么结果并不是把全部a都初始化为2
其中memcpy与strcpy的区别
1.memcpy可以拷贝任何数据类型的对象,指定拷贝的数据长度。
strcpy只能拷贝字符串了,它遇到'\0'就结束拷贝
2.memcpy()效率更高
区别详见:http://www.dewen.org/q/1469
三、用assert来方便调试(包括头文件cassert)
如 cassert(x>=0)如果不满足条件,则会报错
四、关于移位运算
>>:低位溢出,符号位不变,并用符号位补溢出的高位。
<<:符号位不变,低位补0
所以左移一位相当于该数乘以2,左移2位相当于该数乘以2^2=4
右移相反。
使用这样来*2或者/2的好处是直接对数据操作,效率高。
五、常用的计时方法
其实这个是我当时写N皇后问题的计时。
大概一下计时就好了啦(整数)。
首先包含头文件ctime
time_t tm=time(0);
printf("\n计算时间%d秒\n", (int) (time(0) -tm));
或者直接,当然别忘了头文件)。
printf("\n计算时间%.2lf秒\n", (double)clock()/CLOCKS_PER_SEC );
更方便,clock()返回程序目前为止的运行时间,这个时间除以CLOCKS_PER_SEC得到秒为单位。这种方法会包含输入的时间。
可以改进如下:
#include "stdio.h" #include "stdlib.h" #include "time.h" int main( void ) { long i = 10000000L; clock_t start, finish; double duration; /* 测量一个事件持续的时间*/ printf( "Time to do %ld empty loops is ", i ); start = clock(); while( i-- ) ; finish = clock(); duration = (double)(finish - start) / CLOCKS_PER_SEC; printf( "%f seconds\n", duration ); system("pause"); }
六、sscanf sprintf
char buf[512] ;
sscanf("123456", "%s", buf);//此处buf是数组名,它的意思是将123456以%s的形式存入buf中!
printf("%s\n",buf);
sprintf(buf,"%s","12");
printf("%s\n", buf);//结果12
七、位运算与集合运算
异或(^)-开关性质,异或两次后相当于没有异或。
A&B 对应集合交
A|B 对应集合并
A^B 对应集合对称差
一些特殊的模运算可以用位运算来代替,比如模2的整数次幂,我们可以用and运算来代替mod运算,程序段如下(以mod 1048576(=1<<20)为例, y=134328497)。
mod版本:for i:=1 to time do x:=y mod base;
and 版本:for i:=1 to time do x:=y and (1 shl 20-1);
八、随机数发生器
#include<cstdio> #include<cstdlib> #include<ctime> double radom() { //生成[0,1]之间的随机数 return(double)rand()/RAND_MAX; } int radom(int m) { //生成[0,m-1]之间的均匀随机数 return(int)(radom()*(m-1)+0.5); } int main() { srand(time(NULL));//初始化随机数种子 printf("%d\n",radom(100)); }
核心函数是cstdlib中的rand(),它生成一个闭区间[0,RAND_MAX]的均匀随机数(每个整数被产生的概率相同)。RAND_MAX至少为2^15-1=32767,不同环境下不同。
上述的方法先除以RAND_MAX,得到[0,1]之间的随机数,扩大n-1倍之后四舍五入。而不是用rand()%n产生[0,n)内的一个随机数,因为之间取mod的话,可能不能达到预期的效果。(因为RAND_MAX可能只有32767这么大)
请在一开头调用一次srand,而不要再一个程序中多次调用。
感觉java下的随机数简单得多。直接Math.random()就生成了0~1的随机数。
九、strchr、strstr
externchar *strchr(const char *s,char c)返回字符串s中从左往右第一个字符c的指针。
extern char *strstr(char *str1, char *str2);
从字符串str1中查找是否有字符串str2,如果有,从str1中的str2位置起,返回str1中str2起始位置的指针,如果没有,返回null。
十、cctype库
isalpha; //是否字母
iscntrl; //是否控制符
isdigit; //是否是数字
isgraph; //是否字母、数字或标点
islower; //是否小写
isprint; //是否可打印字符
ispunct; //是否标点
isspace; //是否空格
isupper; //是否大写
isxdigit; //是否十六进制数字
tolower; //转为小写
toupper; //转为大写
十一、π
可以这样直接获得:
const double PI=acos(-1.0)
十二、STL map
给出foj 1008 和poj 2403代码
http://poj.org/problem?id=2403
http://acm.fzu.edu.cn/problem.php?pid=1008
#include<iostream> #include<string> #include<map> using namespace std; int main() { int n,m,v; string in; map<string,int> value; cin>>n>>m; while(n--) { cin>>in>>v; value[in]=v; } while(m--) { int sum=0; while(cin>>in,in[0]!='.') { sum+=value[in]; } cout<<sum<<endl; } return 0; }
算法的笔记:
一、二分查找
在有序表中查找元素常常使用二分查找,有时也称折半查找,它的基本思路就是“猜数字游戏”,你心里在想一个不超过1000的正整数,我可以保证在10次之内猜到它----只要你每次告诉我猜的数比你想的大一些、小一些,或者正好猜中。猜的方法就是“二分”,首先我猜500,除了运气特别好之外猜到,都能把范围缩小一半,如果“太大”,那么答案在1~499之间,如果“太小”,那么答案在501~1000之间。只要每次选择区间的中点去猜,每次都可以把可行范围缩小一半。由于log(2) 1000<10,10次一定能猜到。这也是二分查找的基本思路。
只有10%的程序员的能正确写对二分查找。这不是危言耸听,你要把问题分析清楚。
下面给出一段二分代码。
int bsearch(int *a,int L,int R,int v) { int m; while(L<R) { m=L+(R-L)/2; if(a[m]==v) return m; else if(a[m]>v) R=m; else L=m+1; } return -1; }
中点写成m=L+(R-L)/2;而不是m=(R+L)/2;虽然数学上想等,但是这样做可以防止中间过程越界。是一个好习惯。
如果数组中有多个元素都是v,上面的函数返回的是哪一个的下标呢?显然是中间的那个。
那如果要返回最前面的那个呢?
int lower_bound(int *a,int L,int R,int v) { int m; while(L<R) { m=L+(R-L)/2; if(a[m]>=v) R=m; else L=m+1; } return L; }
分析一下这段程序:首先,最后的返回值不仅可能是L,L+1,……R-1,还可能是R----如果v大于A[n-1],就只能插入这里了。这样,尽管查询区间是[L,R),打包返回区间却是[L,R]。A[M]和v的各种关系所带来的影响如下:
A[m]=v:至少已经找到一个,而左边可能还有,因此区间变为[L,m]
A[m]>v:所求位置不可能在后面,但有可能是m,因为区间变为[L,m]
A[m]<v:m和前面都不可行,因此区间变为[m+1,R]
当v存在时,返回它出现的最后一个位置的后面一个位置。如果不存在,返回这样的一个下标i:在此处插入v,原来的元素向后移动一个位置后序列仍然有序。
int upper_bound(int *a,int L,int R,int v) { int m; while(L<R) { m=L+(R-L)/2; if(a[m]<=v) L=m+1; else R=m; } return L; }
这样,设lower_bound和upper_bound的返回值分别为L和R,则v出现的子序列为[L,R)。
若L=R,则区间为空。
顺便说一句,C++ STL中已经包含了upper_bound和lower_bound,可以直接使用。
例如:
LA2678 – Subsequence
http://blog.csdn.net/murmured/article/details/9617487
二、二叉索引树(Fenwick树)也叫树状数组
1.引入:
给定一个n个元素的数组,计算
对于给定的L和R
Query(L,R):计算A(L)+A(L+1)+……A(R)
对于这样的问题,
可以用一个数组c存储前面各个数的和。
这样预处理时间为O(n),而查询时间为O(1)
如果题目改成这样:
给定一个n个元素的数组,计算
对于给定的L和R
Add(x,d):让x增加d。
Query(L,R):计算A(L)+A(L+1)+……A(R)
如果按刚才的每一次Add,都要更新一大堆前缀和。还是会很慢。
此时可以用线段树或者Fenwick树解决。但线段树更加复杂。
因此,这里我们引入“树状数组”,它的修改与求和都是O(logn)的,效率非常高。
树状数组是一个可以很高效的进行区间统计的数据结构。在思想上类似于线段树,比线段树节省空间,编程复杂度比线段树低,但适用范围比线段树小。
定义lowbit(x)为x的二进制表达式中最右边的1所对应的值,(而不是这个比特的序号)
比如,38288的二进制是1001010110010000 lowbit(38288)=16,在程序的实现中,
Lowbit(x)=x&-x;(计算机内部采用补码表示,-x是x按位取反,尾数+1的结果)
对于节点i,如果他是左子结点,那么父结点的编号是i+lowbit(i),如果它是右子结点,那么父结点的编号是i-lowbit(i) ,设Ci为以i结尾的水平长条内的元素之和,如c6=a5+a6顺着结点I往左走,边走边往上爬,沿途经过的ci所对应的长条不重复不遗漏的包含了所有需要累加的元素。
如果修改了一个ai,那么从ci往右走,边走边网上爬,沿途修改所有结点对应的ci即可。
inline int lowbit(int x) { return x&(-x) ; } int sum(int x) { int ans=0; while(x>0) { ans+=C[x]; x-=lowbit(x); } return ans; } void add(int x,int d) { while(x<=N) { C[x]+=d; x+=lowbit(x); } }
代表题目如下:
LA 4329 - Ping pong
http://blog.csdn.net/murmured/article/details/9746801
三、Floyd判圈法
判断重复特别好用。
假设兔子和乌龟在一个直线的跑道上赛跑,同时出发,但兔子速度是乌龟的两倍,所以乌龟永远追不上兔子。但如果是绕圈跑呢?情况就不一样了,兔子将追上乌龟!这也就是这个算法的原理。
例如
UVA 11549 Calculator Conundrum
http://blog.csdn.net/murmured/article/details/9571017
四、RMQ问题
给出一个n个元素的数组A1,A2,……An,设计一个数据结构,支持查询操作Query(L,R),计算min{Al,Al(+1)……Ar}。
最常用的方法是Sparse-Tanle算法,它的预处理时间是O(nlongn),但是查询只需要O(1),而且常数很小。
令d(i,j)表示从i开始的,长度为2^j的一段元素中的最小值,则可以用递推的方法计算d(i,j):d(i,j)=min{ d ( i,j-1 ),d ( j + 2^( j-1) , j-1 ) }
struct RMQ { intd[MAXN][MANX_LOGN]; void init(const vector<int>&X) { int n=X.size(); for(inti=0;i<n;i++) d[i][0]=X[i]; for(int j=1;(1<<j) <=n;j++) for(int i=0; i+ (1<<j) -1 <n; i++) d[i][j]=max(d[i][j-1],d[i+ (1<<(j-1))][j-1] ); } int query(int L,int R) { int k=0; while(1<<(k+1) <= R-L+1) k++; returnmax(d[L][k],d[R - (1<<k) +1][k]); } };
代表题目:
UVA 11235 - Frequent values
http://blog.csdn.net/murmured/article/details/9737425
五、线段树(点修改)
动态范围最小值问题,给出一个有n个元素的数组A1,A2,……An,设计一个数据结构支持以下两种操作:
Update(x,v)把Ax修改为v;
Query(L,R):计算min{Al,A(l+1),……Ar}
如果还是用上面的Sparse-Tanle算法,每次update操作都要重新计算d数组,时间无法承受。故引入线段树。
为了方便,按照从上到下,从坐到右的顺序给所有的结点编号为1,2,3,……,则编号为i的结点,其左右子结点的编号为2i和2i+1
【1,8】
/ \
【1,4】 【5,8】
/ \ / \
【1,2】 【3,4】 【5,6】 【7,8】
/ \ / \ / \ / \
【0,0】 【1,1】 【2,2】【3,3】【4,4】【5,5】 【6,6】【7,7】
设o是当前结点编号,L和R是当前的左右端点(比如,当o=3,L=5,R=8)
查询时,全局变量ql和qr分别代表查询区间的左右端点,修改时,全局变量p和v分别代表修改点的位置和修改后的数值。
struct IntervalTree { int minv[maxnode]; void build(int o, intL, int R) { int M = L + (R-L)/2; if(L == R) minv[o] =A[L]; else { build(o*2, L, M); build(o*2+1, M+1,R); minv[o] =min(minv[o*2], minv[o*2+1]); } } void update(int o, intL, int R) { int M = L + (R-L)/2; if(L == R) minv[o] =v; // 叶结点,直接更新minv else { // 先递归更新左子树或右子树 if(p <= M)update(o*2, L, M); else update(o*2+1, M+1, R); // 然后计算本结点的minv minv[o] =min(minv[o*2], minv[o*2+1]); } } int query(int o, int L,int R) { int M = L + (R-L)/2,ans = INF; if(qL <= L&& R <= qR) return minv[o]; // 当前结点完全包含在查询区间内 if(qL <= M) ans =min(ans, query(o*2, L, M)); // 往左走 if(M < qR) ans =min(ans, query(o*2+1, M+1, R)); // 往右走 return ans; } };
代表题目
UVA 12299 - RMQ with Shifts 线段树
http://blog.csdn.net/murmured/article/details/9882295
六、字典树(Trie也叫前缀树)
struct Trie { int ch[maxnode][sigma_size]; int val[maxnode]; int sz; // 结点总数 void clear() { sz = 1; memset(ch[0], 0, sizeof(ch[0])); } // 初始时只有一个根结点 int idx(char c) { return c - 'a'; } // 字符c的编号 // 插入字符串s,附加信息为v。注意v必须非0,因为0代表“本结点不是单词结点” void insert(const char *s, int v) { int u = 0, n = strlen(s); for(int i = 0; i < n; i++) { int c = idx(s[i]); if(!ch[u][c]) { // 结点不存在 memset(ch[sz], 0, sizeof(ch[sz])); val[sz] = 0; // 中间结点的附加信息为0 ch[u][c] = sz++; // 新建结点 } u = ch[u][c]; // 往下走 } val[u] = v; // 字符串的最后一个字符的附加信息为v }
查询和插入类似。应该根据具体问题具体编写。
数组版感觉比较复杂。
还是用指针吧
直接复制我的LA3942的Trie,需要的时候改改就好 http://blog.csdn.net/murmured/article/details/12951609
struct node { node* next[MLEN]; bool isEnd; node(){ memset(next,0,sizeof(next)); isEnd=false;} }; struct Trie { node *root; inline int index(char &c){return c-'a';} Trie() {root=new node;} void init() {root=new node;} void insert(char *str) { node *p=root; int len=strlen(str); for(int i=0;i<len;i++) { int id=index(str[i]); if(p->next[id]==NULL) { node *t=new node; p->next[id]=t; } p=p->next[id]; if(i==len-1) { p->isEnd=true; return; } } } void query(char *str,int start) { int len=strlen(str); node *p=root; int res=0; for(int i=start;i<len;i++) { int id=index(str[i]); if(p->next[id]==NULL) break; p=p->next[id]; if(p->isEnd) { res+=dp[i+1]; res%=mod; } } dp[start]=res; } void del(node *root) { if(root==NULL) return; for(int i=0;i<26;i++) if(root->next[i]!=0) del(root->next[i]); delete root; } }trie;
代表题目
HDU 1251统计难题
http://blog.csdn.net/murmured/article/details/11262707
七、求全排列
用递归法或者STL的next_permutation
#include<cstdio> #include<algorithm> using namespace std; int a[]={1,2,3}; int len=3; int cnt=0; void perm(int list[], int k, int len) { if (k==len) { for (int i=0; i<len; i++) printf("%d", list[i]); printf("\n"); cnt++; } else for (int i=k; i<len; i++) { swap(list[k],list[i]); perm(list, k+1, len); swap(list[k],list[i]); } } int main() { perm(a,0,len); printf("%d\n\n",cnt); do { for (int i=0; i<len; i++) printf("%d", a[i]); printf("\n"); }while(next_permutation(a,a+len)); }
--------------------------------------------------to becontinue