暑假总结

上个月末在学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个元素的数组,计算
对于给定的LR
Query(L,R):
计算A(L)+A(L+1)+……A(R)

对于这样的问题,
可以用一个数组c存储前面各个数的和。
这样预处理时间为On),而查询时间为O(1)

如果题目改成这样:
给定一个n个元素的数组,计算
对于给定的LR
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

posted @ 2013-09-08 14:06  hr_whisper  阅读(139)  评论(0编辑  收藏  举报