ashang 的停课记录

2024年11月18日

        今天是ashang停课的第一天,昨天接到老师的语音通话,得知冲一把有望进省队,下定决心要停一段时间的课。

今天的主要任务是刷历年NOIP的第一题,并尽量看懂第二题的题解。/太菜了/

基础算法练习:二分算法

洛谷P1314 [NOIP2011 提高组] 聪明的质监员:(参考题解完成)

这道题由题意可得需使用二分查找。

验证二分算法在此题的可行性:

w=0 时,所有矿石都能选;w > max(m) 时,所有矿石都不能选。符合二分算法。

而对于公式y_{i} = \sum_{j=l_{i}}^{r_{i}}[w_{j} \geqslant W] * \sum_{j=l_{i}}^{r_{i}}[w_{j} \geqslant W]v_{j}我们可以考虑前缀和,用pre_n求个数的前缀和,用pre_v求检验值的前缀和。

关于边界的考虑,可以设大一点,如 L=min+2, R=min-2

代码如下:

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int maxn = 2e5 + 5;
const int INF = 0x3f3f3f3f;
int v[maxn], w[maxn], l[maxn], r[maxn];
int n, m, s;
int Y; 
int pre_n[maxn], pre_v[maxn];
int sum;
void init() {
	memset(pre_n, 0, sizeof(pre_n));
	memset(pre_v, 0, sizeof(pre_v));
}
bool check(int W) {
	init();
	Y = 0;
	sum = 0;
	for (int i = 1; i <= n; i++) {
		if (w[i] >= W) {
			pre_n[i] = pre_n[i - 1] + 1;
			pre_v[i] = pre_v[i - 1] + v[i];
		}
		else {
			pre_n[i] = pre_n[i - 1];
			pre_v[i] = pre_v[i - 1];
		}
	}
	for (int i = 1; i <= m; i++) {
		Y += (pre_n[r[i]] - pre_n[l[i] - 1]) * (pre_v[r[i]] - pre_v[l[i] - 1]); 
	}
	sum = llabs(Y - s);
	if (Y > s) return true;
	else return false;
}
signed main() {
//	freopen("1314.in", "r", stdin);
//	freopen("1314.out", "w", stdout);
	int L = INF, R = 0;
	scanf("%lld%lld%lld", &n, &m, &s);
	for (int i = 1; i <= n; i++) {
		scanf("%d%d", &w[i], &v[i]);
		L = min(L, w[i]);
		R = max(R, w[i]);
	}
	for (int i = 1; i <= m; i++) {
		scanf("%d%d", &l[i], &r[i]);
	}
	L -= 1, R += 2;
	int mid;
	int ans = 0x3f3f3f3f3f3f3f3f;
	while (L <= R) {
		mid = (L + R) >> 1;
		if (check(mid)) {
			L = mid + 1;
		}
		else {
			R = mid - 1;
		}
		ans = min(sum, ans);
	}
	printf("%lld\n", ans);
	
	return 0;
}

注意开了long long后的输入输出处理,以及ans的初始值。

MATH_整数与素数

整除

假设 a 和 d 是整数,且 a\neq 0 。那么,如果存在一个整数 k ,使 a=kd ,则称 d 整除 a ,记做 d\mid a 。

如果 d\mid a 且 d> 0 ,称 a 是 d 的倍数,d 是 a 的约数。

特别规定:任何整数 x 都整数 0 ,即 x\mid 0 。

整除的性质:

1. a\mid b 且 b\mid a\rightarrow a= \pm b 

2. 若 b\neq 0a\mid b\rightarrow \left | a \right |\leq \left | b \right |

3. a\mid b\rightarrow a\mid c*b

4. a\mid b 且b \mid c \rightarrow a\mid c

5. a\mid b 且 a\mid c \rightarrow 对任意整数 x 和 y ,a\mid \left ( b*x+c*y \right )

6. 若 m\neq 0\rightarrow a\mid b\Leftrightarrow m*a\mid m*b

7. b\mid a, d\mid a\rightarrow b*d\mid a*c

8. b\mid a, c\mid a且 b 和 c 互质 \rightarrow b*c\mid a

9. b\mid a*c 且 b 和 c 互质 \rightarrow b\mid a

10. 若 a\neq 0,b=q*a+c\rightarrow a\mid b\Leftrightarrow a\mid c 

素数

素数(prime number):在大于1的自然数中,除了1和它本身没有其他约数,这样的数称为素数,否则为合数。

素数又称质数,有无限个;

素数 p 的约数只有两个:1 和 p;

0 和 1 既不是素数也不是合数;

基于乘法运算,素数是构成任意 > 1 整数的原子。

素数判定:试除法

若自然数 n 不能被 \left [ 0, \sqrt{n} \right ] 内的所有数整除,则 n 是素数,时间复杂度:O\left ( \sqrt{n} \right ),适用于n\leq 1e12 。

性质:大于 4 的素数,总是等于 6x+1 或 6x-1 。

bool isPrime(int a) {
    if (a < 2) return 0;
    for (int i = 2; i <= a / i; i++) {
        if (a % i == 0) return 0;
    }
    return 1;
}

枚举约数

试除法枚举 n 的所有约数

约数都是成对出现的,只需枚举其中较小的一个约数即可(复杂度 O(\sqrt{n}) )

#include<bits/stdc++.h>
using namespace std;
const int maxn = 2e3 + 5;
int divisors[maxn], cntd;
void get_divisors(int x) {
	cntd = 0;
	for (int i = 1; i <= x / i; i++) {
		if (x % i == 0) {
			divisors[cntd++] = i;
			if (i != x / i) {
				divisors[cntd++] = x / i;
			}
		}
	}
	sort(divisors, divisors + cntd);
}
int main() {
	int n;
	scanf("%d", &n);
	get_divisors(n);
	for (int i = 0; i < cntd; i++) {
		printf("%d\n", divisors[i]);
	}
	
	
	return 0;
}

例题练习:CF396B

#include<bits/stdc++.h>
using namespace std;
bool isPrime(int n) {
	if (n < 2) return false;
	for (int i = 2; i <= n / i; i++) {
		if (n % i == 0) return false;
	}
	return true;
}

long long Gcd(long long x, long long y) {
	if (y == 0) return x;
	return Gcd(y, x % y);
}

int T;
int main()
{
	scanf("%d", &T);
	while (T--) {
		long long n, v, u;
		scanf("%lld", &n);
		u = n + 1;
		v = n;
		while (!isPrime(u)) u++;
		while (!isPrime(v)) v--;
		long long ans1 = n - u - v + 1, ans2 = (long long)u * v;
		ans1 *= 2, ans2 <<= 1, ans1 = ans2 / 2 + ans1;
		long long d = Gcd(ans1, ans2);
		printf("%lld/%lld\n", ans1 / d, ans2 / d);
	}
	

	return 0;
}

2024年11月19日

继续复习数论。

MATH_整数与素数

素数筛

用于求小于等于 n 的所有素数。

暴力:对于 \leq n 的每个数进行一次素数判定。显然,这种做法的复杂度很高,为 O\left ( n\sqrt{n} \right ) 。

可以考虑:对于任一整数 x ,数 x 的所有倍数一定是合数。这样从小到大枚举 x ,可以避免很多非必要的检测。

基本思路:

从小到大枚举 \leq n 的每个数,把当前这个数的所有倍数标记为合数(筛掉),运行结束时没有被标记的数就是 \leq n 的所有素数。

素数埃氏筛法:求区间 \left [ 2, n \right ] 内的所有素数

· 初始数列: \left \{ 2,3,4,5,6,7,8,9,10,11,12,13,\cdots ,n \right \} 

· 输出最小的素数 \left \{ 2 \right \} ,筛掉 2 的倍数,剩下 \left \{ 3,5,7,9,11,13,\cdots \right \} 

· 输出素数 \left \{ 2,3 \right \} ,筛掉 3 的倍数,剩下 \left \{ 5,7,11,13,\cdots \right \} 

· 输出素数 \left \{ 2,3,5 \right \} ,筛掉 5 的倍数,剩下 \left \{ 7,11,13,\cdots \right \} 

· 继续以上步骤

埃氏筛法的时间复杂度: O(n\cdot \log \left ( \log n \right )) 

筛选次数:n/2+n/3+\cdots +n/n=n(1/2+1/3+\cdots +1/n) 

调和级数:\sum_{i=1}^{n} 1/i=\ln n+\gamma +\varepsilon_{n} (发散速度非常慢

素数分布:\pi \left ( x \right )\approx x/\ln x

P3383 TLE on #5

#include<bits/stdc++.h>
using namespace std;
const int N = 1e6 + 5;
int primes[N], cnt;
bool st[N];
void getPrimes(int n) {
	for (int i = 2; i <= n; i++) {
		if (!st[i]) {
			primes[cnt++] = i;
			for (int j = 2 * i; j <= n; j += i) {
				st[j] = true;
			}
		}
	}
}
int main() {
	int n, q, k;
	scanf("%d%d", &n, &q);
	getPrimes(n);
	while (q--) {
		scanf("%d", &k);
		printf("%d\n", primes[k - 1]);
	}
	
	return 0;
}

埃氏筛法缺陷:一个合数会被多次筛去。

素数线性筛法:(欧拉筛)

每个合数仅被其最小质因子筛去,时间复杂度 O(n) 。

证明 1 :合数仅被其最小质因子筛去

i 循环,扫描 \leq n 的所有数,令 k=i*primes[j]是要筛去的合数

若 i % primes[j] != 0 ---> primes[j] 不是 i 的质因子,且 primes[j] 必小于 i ---> k 的最小质因子是 primes[j]

若 i % primes[j] == 0 ---> primes[j] 必是 i 的最小质因子 ---> k 的最小质因子是 primes[j]

另外,此时,对于 primes 数组的第 j 素数后的所有素数,i * primes[x] 的最小质因子必是 primes[j] ,为避免重复检测,要跳出对 primes 数组的循环。

证明 2 :≤ n 的所有合数均会被筛去

任一合数 k 均有一个最小质因子 p, 则当 i 枚举到 k / p 时一定会筛掉 k

性质:若 n 为合数,则 n 中至少含有一个 \leq \sqrt{n} 的质因子

应用 1 : 基于 \leq \sqrt{n} 的素数,可筛出 ≤ n 的所有素数。例如,基于 ≤ 100 的 25 个素数,可筛出   ≤ 10000 所有素数。

应用 2 :若不超过 \sqrt{n} 的所有素数均不是 n 的约数,则 n 必为素数。(用于改进的多次判定素数,适用于 n \leq 1e12 )

例:判断一个数是否是素数

#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e6 + 5;
int p[maxn], cnt;
bool st[maxn];
void get_primes(int n) {
	for (int i = 2; i <= n; i++) {
		if (!st[i]) p[cnt++] = i;
		for (int j = 0; p[j] * i <= n; j++) {
			st[p[j] * i] = true;
			if (i % p[j] == 0) break;
		}
	}
}
bool is_prime(int x) {
	if (x < maxn) return !st[x];
	for (int i = 0; i < cnt; i++) {
		if (x % p[i] == 0) return false;
		if (p[i] > x / p[i]) break;
	}
	return true;
}
int main() {
	get_primes(maxn - 1);
	long long s;
	scanf("%lld", &s);
	printf("%d\n", is_prime(s));
	
	
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值