【数学1】基础数学问题

  一、【P1469】找筷子(快读+位运算)

我们考虑异或的两个小小的性质:

  1. k 个相同的数的异或和,当 k 为奇数时,结果是这个数本身,否则结果是 0。
  2. 任何数与 0 的异或值是它本身。

然后注意到题目。题目需要求 n 个数中出现奇数次的那个数,且保证这个数存在且只有一个。于是我们根据上面两个性质得出,答案就是这 n 个数的异或和。

 原理:根据性质1,成对的筷子异或后就变成 00 了,只剩下落单的那一根。把这些 00 和落单的那一根的长度异或起来,根据性质2,结果显然就是落单的那一根的长度。

1.1 快读模板

inline int read()
{
	int data = 0, w = 1;
	char c = getchar();
	while (c < '0' || c>'9')
	{
		if (c == '-') w = -1;
		c = getchar();
	}
	while (c >= '0' && c <= '9')
	{
		data = data * 10 + (c - '0');
		c = getchar();
	}
	return data * w;
}

1.2 AC代码

#include <iostream>
#include <string>
#include <algorithm>
#include <cmath>
#include <vector>
#include <cstring>
#include <queue>
#include <map>

using namespace std;

inline int read()
{
	int data = 0, w = 1;
	char c = getchar();
	while (c < '0' || c>'9')
	{
		if (c == '-') w = -1;
		c = getchar();
	}
	while (c >= '0' && c <= '9')
	{
		data = data * 10 + (c - '0');
		c = getchar();
	}
	return data * w;
}

int main()
{
	int n = read();
	int ans = 0;
	for (int i = 1; i <= n; i++)
	{
		int a = read();
		ans = ans ^ a;
	}
	cout << ans;
}

二、【P1029】最大公约数和最小公倍数问题

2.1 最大公约数模板

递归方法:

ll gcd(ll x, ll y)//辗转相除法求公因子
{
	if (y == 0) return x;
	return gcd(y, x % y);
}

最优美方法:

int gcd(int x, int y)
{
    while(y^=x^=y^=x%=y);
    return x;
}

2.2 AC代码

最大公约数和最小公倍数的乘积就是满足条件的两个数的积。

注意爆 int 和 x * y ​是完全平方数的情况。

#include <iostream>
#include <string>
#include <algorithm>
#include <cmath>
#include <vector>
#include <cstring>
#include <queue>
#include <map>

using namespace std;
typedef long long ll;

ll gcd(ll x, ll y)//辗转相除法求公因子
{
	if (y == 0) return x;
	return gcd(y, x % y);
}


int main()
{
	ll x, y, ans = 0, flag = 0;
	cin >> x >> y;
	for (int i = 1; i <= sqrt(1ll * x * y); i++)
	{
		if ((1ll * x * y) % i == 0 && gcd(i, (1ll * x * y) / i) == x)
		{
			ans++;
			if (1ll * i * i == 1ll * x * y) flag = 1;
		}
	}
	cout << ans * 2 - flag; //最后乘以二是因为只遍历了一半
}

三、【P3383】线性筛素数(埃式筛) 

2.1 一些简单筛法

1. 埃式筛:可以一边确认质数,一边筛出合数

//生成不大于 n 的所有质数
bool numlist[100000001];
int prime[20000001], cnt;
void work(int n)
{
	for(int i=2; i<=sqrt(n); i++)
    {
		if(numlist[i]==false)
        {
			prime[++cnt] = i ;
			for(int j=i; i*j<=n; j++)
				numlist[i*j] = true;
		}
	}
	return;
}

2. 欧拉筛:避免重复筛选浪费时间

bool numlist[100000001];
int prime[20000001], cnt;
void work(int n)
{
	for(int i=2; i<=n; i++)
    {
		if(numlist[i]==false)
			prime[++cnt] = i ;
		for(int j=1; j<=cnt && i*prime[j]<=n; j++)
        {
			numlist[i*prime[j]] = true ;
			if(i%prime[j]==0)
				break;
		} 
	}
	return;
}

2.2 AC代码(埃式筛实现)

#include <iostream>
#include <string>
#include <algorithm>
#include <cmath>
#include <vector>
#include <cstring>
#include <queue>
#include <map>

using namespace std;

inline int read()
{
	int data = 0, w = 1;
	char ch = getchar();
	while (ch < '0' || ch>'9')
	{
		if (ch == '-') w = -1;
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9')
	{
		data = 10 * data + ch - '0';
		ch = getchar();
	}
	return data * w;
}

bool check[100000005];
int prime[10000005];

int sift(int n)
{
	memset(check, true, sizeof(check));
	check[0] = false, check[1] = false;
	for (int i = 2; i <= sqrt(n); i++)
	{
		if (check[i])
		{
			for (int j = 2 * i; j <= n; j += i)
				check[j] = false;
		}
	}
	int cnt = 1;
	for (int i = 1; i <= n; i++)
	{
		if (check[i])
		{
			prime[cnt++] = i;
		}
	}
	return cnt - 1;
}


int main()
{
	int n = read();
	int index = sift(n);
	int q = read();
	for (int i = 1; i <= q; i++)
	{
		int a = read();
		cout << prime[a] << endl;
	}
	
}

四、【P1246】编码(组合数问题)

2.1 组合数模板

组合数计算公式:

组合数模板:

int C(int m, int n) //组合数计算
{
	if (m == 0) return 1;
	int res = 1;
	for (int i = n; i > n - m; i--)
		res *= i;
	for (int i = m; i >= 1; i--)
		res /= i;
	return res;
}

2.2 AC代码及思路

本题中“按字典序升序排列”意为:每个单词只能出现一次,并且后一个单词的字典序必须大于前一个单词的字典序。

#include <iostream>
#include <string>
#include <algorithm>
#include <cmath>
#include <vector>
#include <cstring>
#include <queue>
#include <map>

using namespace std;

int C(int m, int n) //组合数计算
{
	if (m == 0) return 1;
	int res = 1;
	for (int i = n; i > n - m; i--)
		res *= i;
	for (int i = m; i >= 1; i--)
		res /= i;
	return res;
}

int main()
{
	string s;	cin >> s;
	int ans = 0;
	int n = s.length();
	for (int i = 1; i < n; i++) //如果不符合字典序
	{
		if (s[i] <= s[i - 1])
		{
			cout << "0";
			return 0;
		}
	}
	for (int i = 1; i < n; i++) //位数少于s的串一定比s小
		ans += C(i, 26);
	for (int i = 0; i < n; i++) //枚举相同位数的每种情况
	{
		for (char j = (i == 0 ? 'a' : s[i - 1] + 1); j < s[i]; j++)
		{
			ans += C(n - 1 - i, 'z' - j); //因为字符串下标从0开始,所以是n-i-1而不是n-i
		}
	}
	cout << ans + 1; //加上自己
	return 0;
}

五、【P1835】素数密度(欧式筛+枚举)

由于本题数据量极大(20亿),所以不能用简单的线性筛法来完成,我们可以先打出一些小素数,然后用这些素数进行二次筛选即可。 

2.1 素数密度求解思路

题目要我们筛出L-R范围内的素数,那么我们只要将这个区间中的合数踢出去就可以了。

恰巧有一个关于判断合数的性质:对于一个合数n,一定可以在1 ~ sqrt(n)范围内找到一个质因子。那么对于量级为20亿的合数,一定可以找到一个小于等于sqrt(20亿)的质因子,约为50000。

大致流程如下: 

  1. 筛出1-50000中的所有质数,并且对合数打上标记。
  2. 在L-R的范围内用我们已求出的质数筛出其中的合数(设p为质数,则i*p一定不为质数),并对其打上标记。
  3. 遍历L~R,没有打标记的元素即为我们所求的素数。

2.2 求解“最小的”“大于n”的“p的倍数”方法

start = max((n + p - 1) / p * p, 2 * p);

 由于这个数肯定在[n,n+p)这个范围内,所以使用(n + p - 1) / p * p,可以轻松得到这个数,但由于用这个计算方法可能把 0 自己判断为满足条件的点(n为0时),所以使用2*p消去干扰。

2.3 AC代码

#include <iostream>
#include <string>
#include <algorithm>
#include <cmath>
#include <vector>
#include <cstring>
#include <queue>
#include <map>

using namespace std;
typedef long long ll;

bool numlist[1000005];
int prime[1000005];
int selt(int n)
{
	memset(numlist, true, sizeof(numlist));
	numlist[0] = false, numlist[1] = false;
	int cnt = 1;
	for (int i = 2; i <= n; i++)
	{
		if (numlist[i])
		{
			prime[cnt++] = i;
			for (int j = 1; j <= cnt && i * prime[j] <= n; j++)
			{
				numlist[i * prime[j]] = false;
				if (i % prime[j] == 0) break;
			}
		}
	}
	return cnt - 1;
}

int main()
{
	ll l, r;	cin >> l >> r;
	ll ans = 0;
	int rev = selt(50000);
	memset(numlist, true, sizeof(numlist));
	for (int i = 1; i <= rev; i++)
	{
		ll p = prime[i];
		ll start = max((l + p - 1) / p * p, 2 * p);
		for (ll j = start; j <= r; j += p)
			numlist[j - l + 1] = false;
	}
	for (int i = 1; i <= r - l + 1; i++)
		if (numlist[i]) ans++;
	if (l == 1) cout << ans - 1; //排除1的干扰
	else cout << ans;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值