寒假第四,五周个人学习总结

这两周主要学习了:1, 筛法求素数。2,阶乘分解质因数。 3,字符串哈希。

1,筛法求素数。

已知素数的倍数(自身除外)是合数,因此在枚举素数时可以一边求出该素数至最大范围的其所有的倍数,并将其标记排除,这可以有效减小时间复杂度。

代码如下:

#include<iostream>
using namespace std;
const int N = 100;
int main()
{
	int primes[N], cnt = 0, n;
	bool flag[N];
	cin >> n;//输入最大范围。
	for(int i=2; i<=n; i++)
		if(!flag[i])//检测该数是否被标记
		{
			primes[cnt++] = i;//记录素数。
			for(int j=i*2; j<=n; j+=i)
				flag[j] = true;//标记合数。
		}
	for(int i=0; i<cnt; i++)//输出。
		cout << primes[i] << endl;
	return 0;
}

2,阶乘分解质因数

任意一个数都可以写成一个或一个以上的质数相乘的形式,如:10 = 2 * 5。当分解一个较小的数的阶乘时可以将其展开,一个一个的求解。如:求5的阶乘的分解质因数。

代码如下:

#include<iostream>
const int N = 1e5+5;
using namespace std;
int prime[N], powers[N], cnt = 0;
void get_prime(int n)//求至n为止所有的质数。 
{
	bool flag[N];
	for(int i=2; i<=n; i++)
		if(!flag[i])
		{
			prime[cnt++] = i;
			for(int j=2*i; j<=n; j+=i)
				flag[j] = true; 
		}
}
int main()
{
	int n;
	cin >> n;
	get_prime(n); 
	for(int i=1; i<=n; i++) // 枚举n阶乘的每个数.
		for(int j=0; j<cnt && prime[j]<=i; j++)
		{
			int p = prime[j], s = 0, k = i;//p, s分别指质数,质数的指数。 
			while(k%p==0)
			{
				s ++;
				k /= p;
			}
			powers[p] += s;//记录p的指数的大小
		}
    //输出
	cout << "!" << n << " = ";
	for(int i=1; i<=n; i++)
	{
		if(powers[i])
			cout << i << "^" << powers[i] << " ";
	}
	cout << endl;
	return 0;
}

但是该代码时间复杂度大,当n稍大一点时,运行时间很长,做题时易超时,因此在求n阶乘的分解质因数时,可以直接1~n一起求解,而不是一个一个的求。

首先我们可以先让n / p (p是质数),求出1~n中只能整除一个p的数的个数,然后再n / p^2,求出1~n中可以整除两个p的数的个数,再n / p^3求出1~n中可以整除三个p的数的个数 ......以此类推,直到p^i (i = 1,2,3,...)大于n, 然后我们再把这些个数相加就等于p在n阶乘中相乘的总次数,即!n = p1^(n/p1 + n/p1^2 + ... + n/p1^i) * p2^(...) * ...(p^i<=n);

代码实现如下:

for(int i=0; i<cnt; i++)
	{
		int p = prime[i], k = n, s = 0;
		while(k)
		{
			s += k / p;
			k /= p;
		}
		powers[p] = s;
	}
	

3,字符串哈希

在比较一个字符串某多个区间的子串或多个字符串是否相同时,暴力遍历易超时,因此可以用字符串哈希的算法。该算法将字符串每个字符变成一个数值,再将字符串转化成p进制并模q得到一个哈希值,判断字符串是否相同只需要判断得到的哈希值是否相同。

为了使得到的哈希值尽量的不冲突,保证哈希值的唯一性,通常p取131或13331,q取2的64次方。在选择模2的64次方时,可以设数值的类型为unsigned long long,利用其自然溢出,自动取2的64次方的模(C中定义无符号型数值溢出自动取2的8*sizeof(type)次方的模)。

求哈希值的公式为:

hash[i] = hash[i-1] * p + num[i],或hash[i] = (hash[i-1] * p + num[i]) % q;

其中hash[i]表示长度为i的字符串前缀的哈希值,num[i]指为字符串i位置设的数值,也可以直接用ASCII码。

公式理解如下:

因为要将字符串转化为p进制,所以字符串中每个字符表示着p进制数中的每个位数上的数,所以在求长度为i的字符串前缀的哈希值时,相当于要将i位置上的数值插入hash[i-1]的p进制形式的最右位,这需要将hash[i-1]的p进制上的数整体向左移一位,即乘以p ,然后再插入字符串i位置上的数值。如不利用unsigned long long 的自动溢出取模,则再模q。

最后在查询时,对于求任意区间[l, r]的子串的哈希值只需:

hash[r] - hash[l-1] * p^(r-l+1);

理解:

查询[l, r]间的哈希值只需将hash[r]p进制形式(r-l+1)位及以后的数全部删掉即可,又因为那些数等于hash[l],如图:

但因为位数差所以hash[r]和hash[l-1]之间不能直接相减,需要将hash[l-1]中所有的数向左移动         r-(l-1)位,也就是相当于hash[l-1] * p^(r-l+1), 再用hash[r]减去它,即hash = hash[r] - hash[l-1] * p[r-l+1];

 3.5,例题

题目

很久很久以前,森林里住着一群兔子。

有一天,兔子们想要研究自己的 DNA 序列。

我们首先选取一个好长好长的 DNA 序列(小兔子是外星生物,DNA 序列可能包含 2626 个小写英文字母)。

然后我们每次选择两个区间,询问如果用两个区间里的 DNA 序列分别生产出来两只兔子,这两个兔子是否一模一样。

注意两个兔子一模一样只可能是他们的 DNA 序列一模一样。

输入格式

第一行输入一个 DNA 字符串 SS。

第二行一个数字 mm,表示 mm 次询问。

接下来 mm 行,每行四个数字 l1,r1,l2,r2l1,r1,l2,r2,分别表示此次询问的两个区间,注意字符串的位置从 11 开始编号。

输出格式

对于每次询问,输出一行表示结果。

如果两只兔子完全相同输出 Yes,否则输出 No(注意大小写)。

数据范围

1≤length(S),m≤1000000

 代码如下:

#include<iostream>
#include<cstring>
#define base 131
using namespace std;
typedef unsigned long long ULL;
const int N = 1e6+5;
char s[N];//字符串 
ULL h[N];//每个前缀的哈希值 
ULL p[N];//p进制的幂 
ULL get(int l, int r) // 查询。 
{
	return h[r] - h[l-1] * p[r-l+1];
}
int main()
{
	scanf("%s", s+1);
	int n = strlen(s+1);
	p[0] = 1;
	for(int i=1; i<=n; i++)
	{
		h[i] = h[i-1] * base + s[i] - 'a' + 1; //记录每个字符串每个前缀的哈希值。 
		p[i] = p[i-1] * base; 
	}
	int t;
	cin >> t;
	while(t--)
	{
		int l1, l2, r1, r2;
		cin >> l1 >> r1 >> l2 >> r2;
		if(get(l1, r1)==get(l2, r2))
			cout << "Yes" << endl;
		else
			cout << "No" << endl;
	}
	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值