上个月末在学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.youkuaiyun.com/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.youkuaiyun.com/murmured/article/details/9746801
三、Floyd判圈法
判断重复特别好用。
假设兔子和乌龟在一个直线的跑道上赛跑,同时出发,但兔子速度是乌龟的两倍,所以乌龟永远追不上兔子。但如果是绕圈跑呢?情况就不一样了,兔子将追上乌龟!这也就是这个算法的原理。
例如
UVA 11549 Calculator Conundrum
http://blog.youkuaiyun.com/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.youkuaiyun.com/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.youkuaiyun.com/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.youkuaiyun.com/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.youkuaiyun.com/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