(Week 16)综合复习(C++,数学)

T1 [Daimayuan]RSA(C++,数学)

R S A RSA RSA算法选择两个不同质数的积作为模数。现在有两个正整数 A , B A,B A,B,如果它们是不同的质数,则判定为 full credit;否则,如果 A ⋅ B A⋅B AB不是任意大于 11 11 11的整数的平方的整数倍,则判定 partial credit;否则判定为no credit

输入格式

一行两个正整数 A , B A,B A,B

输出格式

full creditpartial creditno credit

样例1输入
13 23
样例1输出
full credit
样例2输入
3 3
样例2输出
no credit
数据规模

所有数据保证 2 ≤ A , B ≤ 1 0 12 2≤A,B≤10^{12} 2A,B1012

解题思路

首先是判断两个数是否相同,相同的话可以直接输出no credit了;如果不同再进行质数判断

由于数据范围较大,需要对常规质数算法进行优化,优化原理是因子总是成对出现

如果都是质数,我们可以输出full credit;如果不是均为质数,我们再判断乘积是否能被 i 2 ( i > 1 ) i^2(i>1) i2(i>1)整除

但在这里需要注意,由于数据范围是 1 0 12 10^{12} 1012,也就是说乘积的范围已经超过了long long的范围,我们需要想其他的办法进行判断:

(1)单独判断两个数是否能被整除,如果能,输出no credit;如果不能,执行下一步

(2)判断两个数的最大公约数是否 > 1 >1 >1,如果是,输出no credit;如果否,输出partial credit

前者很好理解,关于后者,这里提供一下证明

在两个数已经确认都不能被整除的情况下,如果乘积仍然能被整除,那么唯一的可能性就是两个数分别提供一个 i i i,那么 i i i就是这两个数的公共因子,而我们取其最大的公共因子,如果这个因子不是 1 1 1,那么就可以判断能被 i 2 ( i > 1 ) i^2(i>1) i2(i>1)整除

AC代码如下

#include <iostream>
using namespace std;

long long a, b;


long long gcd(long long a, long long b) {
	long long t;
	while (b != 0) {
		t = a % b;
		a = b;
		b = t;
	}
	return a;
}


int main() {
	cin >> a >> b;
	bool is_all_prime = true;
	bool can_be_divided = false;
	if (a == b) is_all_prime = false;
	if (is_all_prime) {
		for (long long i = 2; i * i <= a; i++) {
			if (a % i == 0) {
				is_all_prime = false;
			}
			if (a % (i * i) == 0) {
				can_be_divided = true;
			}
		}
	}
	if (is_all_prime) {
		for (long long i = 2; i * i <= b; i++) {
			if (b % i == 0) {
				is_all_prime = false;
			}
			if (b % (i * i) == 0) {
				can_be_divided = true;
			}
		}
	}
	if (is_all_prime) {
		cout << "full credit" << endl;
		return 0;
	}
	if (!can_be_divided && gcd(a, b) == 1) {
		cout << "partial credit" << endl;
	}
	else {
		cout << "no credit" << endl;
	}
	return 0;
}

T2 [Daimayuan]数组操作(C++,字符串)

给你一个有 n n n 个元素的数组 a a a 。你可以对它进行如下操作,次数不限。

从一个偶数大小为 2 k 2k 2k 的数组中选择一些从位置 l l l 开始的子数组( 1 ≤ l ≤ l + 2 ∗ k − 1 ≤ n 1≤l≤l+2*k−1≤n 1ll+2k1n , k ≥ 1 k≥1 k1) ,对于 0 0 0 k − 1 k−1 k1(包括)之间的每一个 i i i ,将值 a l + k + i a_l+k+i al+k+i 分配给 a l + i a_l+i al+i

例如,如果 a = [ 2 , 1 , 3 , 4 , 5 , 3 ] a=[2,1,3,4,5,3] a=[2,1,3,4,5,3] ,然后选择 l = 1 l=1 l=1 k = 2 k=2 k=2 ,应用这个操作,数组将变成 a = [ 3 , 4 , 3 , 4 , 5 , 3 ] a=[3,4,3,4,5,3] a=[3,4,3,4,5,3]

请找出使数组中所有元素相等所需的最少操作数(可能是零)。

输入格式

输入由多个测试用例组成。第一行输入一个整数 t t t( 1 ≤ t ≤ 2 × 1 0 4 1≤t≤2×10^4 1t2×104)表示测试用例的数量。

每个测试用例的包含 ( n + 1 ) (n+1) (n+1) 个整数:

第一个整数 n n n( 1 ≤ n ≤ 2 × 1 0 5 1≤n≤2×10^5 1n2×105) 表示数组的长度。

此后 n n n 个整数 a 1 , a 2 , . . . , a n a_1,a_2,...,a_n a1,a2,...,an( 1 ≤ a i ≤ n 1≤a_i≤n 1ain) 表示数组的元素。

输出格式

输出 t t t 行,每行一个整数表示用给定的操作使数组中所有元素相等所需的最小操作数。

样例输入
5
3 1 1 1
2 2 1
5 4 4 4 2 4
4 4 2 1 3
1 1
样例输出
0
1
1
2
0
数据规模

保证所有测试用例的 n n n 之和不超过 200000 200000 200000

提示

在第一个测试中,所有元素都是相等的,因此不需要任何操作。

在第二个测试中,你可以应用一个操作, k = 1 k=1 k=1 l = 1 l=1 l=1,设置 a 1 ← a 2 a_1←a_2 a1a2 ,通过 1 1 1 个操作,数组变成 [ 1 , 1 ] [1,1] [1,1]

在第三个测试中,你可以应用一个操作, k = 1 k=1 k=1 l = 4 l=4 l=4,设置 a 4 ← a 5 a_4←a_5 a4a5 ,然后数组变成 [ 4 , 4 , 4 , 4 , 4 ] [4,4,4,4,4] [4,4,4,4,4]

在第四个测试中,你可以应用一个操作, k = 1 k=1 k=1 l = 3 l=3 l=3,设置 a 3 ← a 4 a_3←a_4 a3a4 ,数组变成 [ 4 , 2 , 3 , 3 ] [4,2,3,3] [4,2,3,3],然后你可以应用另一个操作, k = 2 k=2 k=2 l = 1 l=1 l=1,设置 a 1 ← a 3 a_1←a_3 a1a3, a 2 ← a 4 a_2←a_4 a2a4, 数组变成 [ 3 , 3 , 3 , 3 ] [3,3,3,3] [3,3,3,3]

在第五次测试中,只有一个元素,因此不需要任何操作。

解题思路

首先解释一下有点难懂的题意

简单来说就是 a 1 , a 2 , . . . , a k a_1,a_2,...,a_k a1,a2,...,ak <- a k + 1 , a k + 2 , . . . a 2 k a_{k+1},a_{k+2},...a_{2k} ak+1,ak+2,...a2k(要求一一对应)

举个例子,一个长度为 2 n 2n 2n的数组,我们可以用后 n n n个元素赋值给前 n n n个元素

但是所有的操作数是连续的,也就是说,对于一个长度为 2 n + 1 2n+1 2n+1的数组,你不能用后 n n n个元素赋值给前 n n n个元素,因为中间有一个间隔元素

接下来讲解解题思路

根据题意,我们只能把后面的数赋值给前面,而不能反过来,所以最后所有数一定都会变为最后一个数

那么我们从后方开始操作数组,累计当前已经成功匹配的长度

当我们尝试匹配下一个元素的时候,有两种情况:

(1)匹配成功,不需要操作:将当前匹配长度 + 1 +1 +1continue

(2)匹配失败,需要操作:将之前已经匹配的所有元素向前赋值

不断循环,直到所有元素都匹配成功

最后,AC代码如下

#include <iostream>
using namespace std;
const int max_n = 2e5;

int t, n, num_arr[max_n];


int main() {
	cin >> t;
	for (int i = 0; i < t; i++) {
		cin >> n;
		for (int i = 0; i < n; i++) {
			cin >> num_arr[i];
		}
		int match = num_arr[n - 1];
		int len = 0, ans = 0;
		for (int i = n - 1; i >= 0; i--) {
			if (num_arr[i] == match) len++;
			else {
				i -= len - 1;
				len *= 2;
				ans++;
			}
		}
		cout << ans << endl;
	}
	return 0;
}

T3 [Daimayuan]A-B 数对(C++,离散化)

给出一串数以及一个数字 C C C ,要求计算出所有 A − B = C A−B=C AB=C 的数对的个数(不同位置的数字一样的数对算不同的数对)。

输入格式

输入共两行。

第一行,两个整数 N N N, C C C

第二行, N N N 个整数,作为要求处理的那串数。

输出格式

一行,表示该串数中包含的满足 A − B = C A−B=C AB=C 的数对的个数。

样例输入
4 1
1 1 2 3
样例输出
3
数据范围

1 ≤ N ≤ 2 × 1 0 5 1≤N≤2×10^5 1N2×105, 1 ≤ C ≤ 2 × 1 0 5 1≤C≤2×10^5 1C2×105, 题目保证输入的 N N N 个数范围小于 2 30 2^{30} 230

解题思路

对于枚举所有 A A A B B B的组合的想法,直接 p a s s pass pass,因为时间复杂度太高了

那么我们回归题意:

(1) A A A B B B之间的差值固定

(2)不同位置的相同数字看作不同数字

所以我们决定对输入的数据进行处理,排序并单独累计每一个数字出现的次数

之后的计算就很简单了

int l = 0, r = 1;//保存B, A的索引
while (r <= idx) {
	if (num[r] - num[l] == c) {//A - B = c
		;//累计ans
	}
	else if (num[r] - num[l]) {//A - B > c
		l++;
	}
	else {//A - B < C
		r++;
	}
}

最后,AC代码如下

#include <iostream>
#include <cmath>
#include <algorithm>
using namespace std;
const int max_num = pow(2, 30);
const int max_n = 2e5;
const int max_c = 2e5;

int num_arr[max_n], n, c;
int discre_num_arr[max_n];
int sum_arr[max_n];

int main() {
	cin >> n >> c;
	for (int i = 0; i < n; i++) cin >> num_arr[i];
	sort(num_arr, num_arr + n);
	
	int idx = 0, cur_num = num_arr[0];
	discre_num_arr[idx] = cur_num;
	for (int i = 0; i < n; i++) {
		if (cur_num == num_arr[i]) {
			sum_arr[idx]++;
		}
		else {
			cur_num = num_arr[i];
			idx++;
			discre_num_arr[idx] = cur_num;
			sum_arr[idx]++;
		}
	}

	int l = 0, r = 1;
	long long ans = 0;
	while (r <= idx) {
		if (discre_num_arr[r] - discre_num_arr[l] == c) {
			ans += sum_arr[r] * sum_arr[l];
			r++; l++;
		}
		else if (discre_num_arr[r] - discre_num_arr[l] < c) {
			r++;
		}
		else {
			l++;
		}
	}

	cout << ans << endl;
	return 0;
}

T4 [Daimayuan]数位计算(C++,数学)

给出一个整数 n n n,请解决下面的问题:

使 f ( x ) = f(x)= f(x)=(不超过 x x x 且与 x x x 具有相同位数的正整数的个数)。

求出 f ( 1 ) + f ( 2 ) + . . . + f ( n ) f(1)+f(2)+...+f(n) f(1)+f(2)+...+f(n) ,结果对 998244353 998244353 998244353 取模。

输入格式

一个整数 N N N

输出格式

一个整数——上面问题的答案,并对 998244353 998244353 998244353 取模。

样例输入1
16
样例输出
73

样例解释:对从 1 1 1 9 9 9 的每个 x x x,不超过 x x x 且与 x x x 具有相同位数的正整数有 1 , 2 , . . , x 1,2,..,x 1,2,..,x,因此, f ( 1 ) = 1 , f ( 2 ) = 2 , . . . , f ( 9 ) = 9 f(1)=1,f(2)=2,...,f(9)=9 f(1)=1,f(2)=2,...,f(9)=9。对从 10 10 10 16 16 16 的每个 x x x,不超过 x x x 且与 x x x 具有相同位数的正整数有 10 , 11 , . . , x 10,11,..,x 10,11,..,x,因此, f ( 10 ) = 1 , f ( 11 ) = 2 , . . . , f ( 16 ) = 7 f(10)=1,f(11)=2,...,f(16)=7 f(10)=1,f(11)=2,...,f(16)=7。所以答案为 73 73 73

样例输入2
238
样例输出2
13870
样例输入3
999999999999999999
样例输出3
762062362
数据规模

所有数据保证 1 ≤ N < 1 0 18 1≤N<10^{18} 1N<1018,且 N N N 是整数。

解题思路

f ( x ) f(x) f(x)具有很强的规律性:

1 1 1~ 9 9 9 x x x f ( x ) f(x) f(x)和为 ∑ i = 1 9 i \sum_{i=1}^9{i} i=19i

10 10 10~ 99 99 99 x x x f ( x ) f(x) f(x)和为 ∑ i = 1 90 i \sum_{i=1}^{90}{i} i=190i

100 100 100~ 999 999 999 x x x f ( x ) f(x) f(x)和为 ∑ i = 1 900 i \sum_{i=1}^{900}{i} i=1900i

. . . ... ...

我们只需要对不同长度的数字分别求和,最后再加在一起就可以了

*注:由于答案过大,在用求和公式 ( 1 + n ) ∗ n / 2 (1 + n) * n / 2 (1+n)n/2的时候,不能求出计算结果再取模,要对 n n n取模再求计算结果。而如果要对 n n n取模,一定要用 998244353 ∗ 2 998244353 * 2 9982443532而不是 998244353 998244353 998244353

AC代码如下

#include <iostream>
using namespace std;
const long long max_n = 1000000000000000000;
const long long mod_num = 998244353;

int main() {
	long long n;
	cin >> n;
	long long temp = n, len = 0;
	while (temp) {
		len++; temp /= 10;
	}
	long long ans = 0; temp = 9;
	for (int i = 1; i < len; i++) {
		ans = (ans + (1 + (temp % (2 * mod_num))) * (temp % (2 * mod_num)) / 2) % mod_num;
		temp *= 10;
	}
	temp = n - temp / 9 + 1;
	ans = (ans + (1 + (temp % (2 * mod_num))) * (temp % (2 * mod_num)) / 2) % mod_num;
	cout << ans << endl;
	return 0;
}

T5 [Daimayuan]新国王游戏(C++,数学)

又到了 H H H 国国庆, 国王再次邀请 n n n 位大臣来玩有奖游戏。上次国庆被众臣吐槽国王小气后,国王决定今年大方点,改变游戏规则且不再参与游戏,免得被大臣们质疑。首先, 他让每位大臣在左、 右手上面分别写下一个正整数。然后让这 n n n 位大臣排成一排。排好队后, 所有的大臣都会获得国王奖赏的若千金币, 每位大臣获得的金币数分别是:排在该大臣后面的所有人的左手上的数的乘积乘以他自己右手上的数。国王希望所有大臣获得的金币数之和最多,所以他想请你帮他重新安排一下队伍的顺序。

简而言之,给定 n n n 对数 a i , b i a_i,b_i ai,bi,找到一种排列顺序,使得 b 1 × a 2 ∗ a 3 ∗ … a n + b 2 × a 3 ∗ a 4 ∗ … a n + ⋯ + b n b_1×a_2∗a_3∗…a_n+b_2×a_3∗a_4∗…a_n+⋯+b_n b1×a2a3an+b2×a3a4an++bn 最大,求最大值,由于答案可能很大,需要对 1000000007 1000000007 1000000007 取模

输入格式:

第一行,包含一个整数 n n n。 第二行到第 n + 1 n+1 n+1 行,包含两个整数 a i , b i a_i,b_i ai,bi

输出格式:

输出一行,表示按某种排序后的 b 1 × a 2 ∗ a 3 ∗ … a n + b 2 × a 3 ∗ a 4 ∗ … a n + ⋯ + b n b_1×a_2∗a_3∗…a_n+b_2×a_3∗a_4∗…a_n+⋯+b_n b1×a2a3an+b2×a3a4an++bn 的最大值对 1000000007 1000000007 1000000007 取模的结果

样例输入
2
1 2
3 4
样例输出
10
说明

只有两种情况:

1. 1. 1.

1 2
3 4

( 2 ∗ 3 ) + 4 = 10 (2∗3)+4=10 (23)+4=10

2. 2. 2.

3 4
1 2

( 4 ∗ 1 ) + 2 = 6 ( 4 ∗ 1 ) + 2 = 6 (4∗1)+2=6(4∗1)+2=6 (41)+2=6(41)+2=6

所以答案为 10 10 10

数据限制

对于 100 100 100% 的数据,保证 1 ≤ n ≤ 1 0 6 1≤n≤10^6 1n106, 1 ≤ a i , b i ≤ 2 30 1≤a_i,b_i≤2^{30} 1ai,bi230

解题思路

思路很简单:先排序,然后累加求和

解题关键在于如何排序

采用算法sort()进行排序,我们需要做的就是重载比较运算符,而重载比较运算符,我们就只需要考虑一种简单的比较情形:相邻对换

对于 i , i + 1 i,i+1 i,i+1两位大臣,我们不需要考虑其 i i i前面的人和 i + 1 i+1 i+1后面的人,因为他们的顺序是不变的

我们只需要考虑两位大臣调换前后二者获取的金币变多了还是变少了

在调换之前: b i ∗ a i + 1 + b i + 1 b_i*a_{i+1}+b_{i+1} biai+1+bi+1

在调换之后: b i + 1 ∗ a i + b i b_{i+1}*a_i+b_i bi+1ai+bi

比较二者的大小即可知道哪种排序获得的金币更多

AC代码如下

#include <iostream>
#include <algorithm>
#include <cmath>
using namespace std;
const int max_n = 1e6;
const int max_ab = pow(2, 30);
const int mod_num = 1000000007;

struct person { long long l, r; }persons[max_n + 1];

int main() {
	int n;
	cin >> n;
	for (int i = 1; i <= n; i++) cin >> persons[i].l >> persons[i].r;
	sort(persons + 1, persons + 1 + n, [](person p1, person p2) {
		return p1.r * p2.l + p2.r > p2.r * p1.l + p1.r;
		});
	long long ans = 0;
	long long sum = 1;
	for (int i = n; i > 0; i--) {
		ans = (ans + persons[i].r * sum) % mod_num;
		sum = (sum * persons[i].l) % mod_num;
	}
	cout << ans << endl;
	return 0;
}

T6 [Daimayuan]完美数(C++,数学)

对于给定的数字 a a a , b b b ,当整数 n n n 在十进制下的所有数位都为 a a a b b b 时,我们称 n n n 是“好数”

对于好数 n n n ,当 n n n 在十进制下每一位的数字之和也为“好数”时,我们称 n n n 是一个“完美数”

请你求出有多少 m m m 位数是“完美数”

输入格式

输入一行三个整数 a a a , b b b , m m m , 含义如题面所示 ( 1 ≤ m ≤ 1 0 6 1≤m≤10^6 1m106, 1 ≤ a , b ≤ 9 1≤a,b≤9 1a,b9)。

输出格式

输出一行一个整数表示完美数的数量 , 由于答案可能很大 , 请你将答案对 1 0 9 + 7 10^9+7 109+7 取模

样例输入
5 1 5
样例输出
1
样例解释

只有 11111 11111 11111 满足要求

解题思路

一个长度为 m m m的数字,每个数字上的数只有两种可能

f o r for for循环枚举 a a a的数量 i i i,计算 a ∗ i + b ∗ ( m − i ) a*i+b*(m-i) ai+b(mi)是否为好数,以此方法找出所有完美数

如果符合条件,我们的计算公式为 C m i C_m^i Cmi

由于含有阶乘的计算,很容易爆精度,所以我们想要取模

但是要知道,取模运算对于除法是不成立的

于是,我们引入逆元

首先是逆元的定义:在 m o d   p mod\ p mod p的意义下,有 ( a ∗ b )   m o d   p ≡ 1 (a*b)\ mod\ p\equiv1 (ab) mod p1,我们称 b b b a a a的乘法逆元

逆元有这样一个性质: a / b   m o d   p = a ∗ i n v ( b )   m o d   p a/b\ mod\ p=a*inv(b)\ mod\ p a/b mod p=ainv(b) mod p i n v ( b ) inv(b) inv(b) b b b的乘法逆元)

也就是说,逆元可以将 m o d   p mod\ p mod p意义下的除法转化为乘法,然后我们就可以进行取模运算了

最后,AC代码如下

#include <iostream>
using namespace std;
const int max_m = 1e6;
const int mod_num = 1e9 + 7;

int a, b, m;
long long factorial[max_m + 1];

bool judge(int sum) {
	int t = 0;
	while (sum) {
		t = sum % 10;
		sum /= 10;
		if (!(t == a || t == b)) return false;
	}
	return true;
}

long long pow(long long x, long long y, long long p) {//快速幂
	long long ret = 1;
	while (y) {
		if (y & 1) ret = ret * x % p;
		x = x * x % p;
		y >>= 1;
	}
	return ret;
}

long long inv(long long x, long long p) {//求解mod p意义下,x的逆元
	return pow(x, p - 2, p);
}

long long cmp(long long m, long long n) {
	return factorial[n] * inv(factorial[m], mod_num) % mod_num * inv(factorial[n - m], mod_num) % mod_num;
}

int main() {
	factorial[0] = 1;
	for (int i = 1; i <= max_m; i++)
		factorial[i] = (factorial[i - 1] * i) % mod_num;

	cin >> a >> b >> m;
	long long ans = 0;
	for (int i = 0; i <= m; i++) {
		int sum = a * i + b * (m - i);
		if (judge(sum)) ans = (ans + cmp(i, m)) % mod_num;
	}
	cout << ans << endl;
	return 0;
}

T7 [Daimayuan]Lusir的游戏(C++,搜索)

L u s i r Lusir Lusir 正在玩一个古老的基于 D O S DOS DOS 的游戏。

游戏中有 N + 1 N+1 N+1 座建筑——从 0 0 0 N N N 编号,从左到右排列。编号为 0 0 0 的建筑高度为 0 0 0 个单位,编号为 i i i 的建筑高度为 H ( i ) H(i) H(i) 个单位。 起初, L u s i r Lusir Lusir 在编号为 0 0 0 的建筑处。每一步,它跳到下一个(右边)建筑。假设 L u s i r Lusir Lusir 在第 k k k 个建筑,且它现在的能量值是 E E E,下一步它将跳到第 k + 1 k+1 k+1 个建筑。

如果 H ( k + 1 ) > E H(k+1)>E H(k+1)>E,那么 L u s i r Lusir Lusir 就失去 H ( k + 1 ) − E H(k+1)−E H(k+1)E 的能量值,否则他将得到 E − H ( k + 1 ) E−H(k+1) EH(k+1) 的能量值。

游戏目标是到达第 N N N 个建筑,在这个过程中能量值不能为负数个单位。

现在的问题是 L u s i r Lusir Lusir 至少以多少能量值开始游戏,才可以保证成功完成游戏?

输入格式

第一行输入整数 N N N。 第二行是 N N N 个空格分隔的整数, H ( 1 ) , H ( 2 ) , … , H ( N ) H(1),H(2),…,H(N) H(1),H(2),,H(N) 代表建筑物的高度。

输出格式

输出一个整数,表示所需的最少单位的初始能量值上取整后的结果。

数据范围

1 ≤ N , H ( i ) ≤ 1 0 5 1≤N,H(i)≤10^5 1N,H(i)105

输入样例1:
5
3 4 3 2 4
输出样例1:
4
解题思路

答案具有单调性:能量越多,我们成功通关的可能性越大

所以采用二分搜索答案,如果能够成功通关,我们去中点左侧搜索,如果不能,我们去中点右侧搜索

至于如何判断,采用模拟即可

*注:模拟时需要注意一点,当能量过高时,之后的能量可能会以指数形式增长,导致直接爆long long造成判断异常。这里简单加上一个判断条件:如果当前能量大于或等于最高的建筑,我们就可以判断能够通关,因为之后的能量只可能增不会减

AC代码如下

#include <iostream>
using namespace std;
const int max_n = 1e5;
const int max_h = 1e5;

int buildings[max_n + 1], n, max_value = -1;


bool judge(long long e) {
	for (int i = 1; i <= n; i++) {
		e += e - buildings[i];
		if (e < 0) return false;
		if (e >= max_value) return true;
	}
	return true;
}


int bin_search() {
	int l = -1, r = max_value + 1;
	while (l + 1 != r) {
		int m = (l + r) / 2;
		if (judge(m)) r = m;
		else l = m;
	}
	return r;
}


int main() {
	cin >> n;
	for (int i = 1; i <= n; i++) {
		cin >> buildings[i];
		max_value = max(max_value, buildings[i]);
	}
	cout << bin_search() << endl;
	return 0;
}

T8 [Daimayuan]BFS练习1(C++,搜索)

给你一个数字 a a a,每次可以选择下面四种操作中的一种:

  1. 把数字 a a a加上一。
  2. 把数字 a a a乘以 2 2 2
  3. 把数字 a a a乘以 3 3 3
  4. 把数字 a a a减去一。

问把这个 a a a变成 b b b最少需要多少步。

你要回答 q q q个询问, b 1 , b 2 , … , b q b_1,b_2,…,b_q b1,b2,,bq,输出把 a a a变成 b 1 , b 2 , … , b q b_1,b_2,…,b_q b1,b2,,bq的最小步数。

输入格式

第一行两个整数 a , q a,q a,q

接下来一行 q q q个整数 b 1 , … , b q b_1,…,b_q b1,,bq

输入格式

输出 q q q个数字,分别表示把 a a a变成 b 1 , b 2 … , b q b_1,b_2…,b_q b1,b2,bq的最小步数。

样例输入
3 10
1 2 3 4 5 6 7 8 9 10
样例输出
2 1 0 1 2 1 2 2 1 2
数据规模

对于所有数据,保证 1 ≤ a , q , b i ≤ 1 0 5 1≤a,q,b_i≤10^5 1a,q,bi105

解题思路

本题采用 B F S BFS BFS解决,虽然这个看题目就知道了,但这里还是说明一下为什么

首先,思路是这样的:我们每次需要尝试所有的 4 4 4种操作,如果到达新格子,记录当前操作步数,如果到达已经到达过的格子,不做操作

因为我们每次只能在上一步的基础上进行,所以需要维护上一步的状态

而已经到达过的格子不需要再次搜索

以上所有特征与 B F S BFS BFS完全吻合,所以采用

AC代码如下

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <queue>
using namespace std;
const int max_q = 1e5;

int ans[max_q + 1];
int a, q, b;
queue<pair<int, int>>bfsq;


void bfs(int a) {
	bfsq.push({ a,0 });
	while (!bfsq.empty()) {
		auto head = bfsq.front();
		bfsq.pop();
		if (ans[head.first] == -1) ans[head.first] = head.second;
		if (head.first + 1 <= max_q && ans[head.first + 1] == -1) bfsq.push({ head.first + 1,head.second + 1 });
		if (head.first - 1 > 0 && ans[head.first - 1] == -1) bfsq.push({ head.first - 1,head.second + 1 });
		if (head.first * 2 <= max_q && ans[head.first * 2] == -1) bfsq.push({ head.first * 2,head.second + 1 });
		if (head.first * 3 <= max_q && ans[head.first * 3] == -1) bfsq.push({ head.first * 3,head.second + 1 });
	}
}


int main() {
	memset(ans, -1, sizeof(int) * (max_q + 1));
	scanf("%d %d", &a, &q);
	bfs(a);
	for (int i = 0; i < q; i++) {
		scanf("%d", &b);
		printf("%d ", ans[b]);
	}
	return 0;
}

T9 [Daimayuan]01序列2(C++,数学)

又是大家最喜欢的 01 01 01序列问题了呢

这次的问题非常的简单, c c cc cc觉得一个 01 01 01序列中两个1之间至少要有 k k k 0 0 0,现在他要构造出一个长度为 n n n 01 01 01序列,请问他有多少种不同的构造方法

这个数字可能会非常大,请你对 1 0 9 + 7 10^9+7 109+7取模

输入格式

一行,给出两个整数 n n n k k k

输出格式

一个整数,代表不同的构造方法数

数据范围

1 ≤ n ≤ 1 0 6 1≤n≤10^6 1n106

0 ≤ k < n 0≤k<n 0k<n

样例输入
4 2
样例输出
6
解题思路

首先理解一下题意:符合要求的 01 01 01序列中可能有 [ 0 , n + k k + 1 + 1 ] [0,\frac{n + k}{k + 1}+1] [0,k+1n+k+1] 1 1 1,每两个 1 1 1之间必须有 k k k 0 0 0

那么我们枚举 1 1 1的数量,累计含有不同 1 1 1 01 01 01序列总数即可

接下来说明如何枚举

枚举 1 1 1的数量很简单,直接 f o r for for循环

然后是计算含有 i i i 1 1 1 01 01 01序列的种类数

由于每两个 1 1 1之间必须有 k k k 0 0 0,我们将其提前分配出去以求简化问题

那么现在有 i + 1 i+1 i+1个区间,我们要将 n − ( i − 1 ) ∗ k − i n-(i-1)*k-i n(i1)ki 0 0 0分配到这些区间中

这时我们想到了数学组合数问题中常见的隔板法

隔板法:有 i i i个相同的物品,要求分成 j j j组,问有多少种分配方式(每组中至少分配 1 1 1个物品)

计算公式就是 C i − 1 j − 1 C_{i-1}^{j-1} Ci1j1

那么为了使用这个方法,我们从 i + 1 i+1 i+1个区间中抽取出 1 1 1 0 0 0(因为要求每组中至少分配一个物品)

所以我们的计算公式变成了 C ( n − ( i − 1 ) ∗ k − i + i + 1 ) − 1 ( i + 1 ) − 1 C_{(n-(i-1)*k-i+i+1)-1}^{(i+1)-1} C(n(i1)ki+i+1)1(i+1)1

现在新的问题出现了,计算组合数 C n m C_n^m Cnm需要计算 n ! m ! ( n − m ) ! \frac{n!}{m!(n-m)!} m!(nm)!n!,我们都知道阶乘会爆精度,所以想要取模

但是取模操作是不能对除法使用的

这时候就需要引入逆元

逆元:在 m o d   p mod\ p mod p的意义下,有 ( a ∗ b )   m o d   p ≡ 1 (a*b)\ mod\ p\equiv1 (ab) mod p1,我们称 b b b a a a的乘法逆元

那么这和我们遇到的问题有什么关系呢?

逆元(为了说明方便,之后称 i n v ( x ) inv(x) inv(x) x x x的逆元)有这样一个性质: a / b   m o d   p = a ∗ i n v ( b )   m o d   p a/b\ mod\ p=a*inv(b)\ mod\ p a/b mod p=ainv(b) mod p

将除法取模转换为乘法取模,我们的问题就解决了

n ! m ! ( n − m ) !   m o d   p = n ! ∗ i n v ( m ! ) ∗ i n v ( ( n − m ) ! )   m o d   p \frac{n!}{m!(n-m)!}\ mod\ p=n!*inv(m!)*inv((n-m)!)\ mod\ p m!(nm)!n! mod p=n!inv(m!)inv((nm)!) mod p

接下来是最后一个问题:如何求逆元

我在这里提供两个方法

(1)费马小定理: b p − 1   m o d   p = 1 b^{p-1}\ mod\ p=1 bp1 mod p=1

所以 b p − 2 b^{p-2} bp2即为 m o d   p mod\ p mod p意义下, b b b的乘法逆元

long long pow(long long x, long long y, long long p) {//快速幂
	long long ret = 1;
	while (y) {
		if (y & 1) ret = (ret * x) % p;
        ret = (ret * ret) % p;
		y >>= 1;
	}
	return ret;
}

long long inv(long long x, long long p) {//mod p意义下,x的逆元
	return pow(x, p - 2, p);
}

(2)扩展欧几里得

void exgcd(long long a, long long b, long long& x, long long& y) {//扩展欧几里得
	if (!b) {
        x = 1;
        y = 0;
    }
    else {
        exgcd(b, a % b, y, x);
        y = y - x * (a / b);
    }
}

long long inv(long long a, long long b) {
	long long x, y;//x为mod b意义下,a的逆元;y为mod a意义下,b的逆元
    exgcd(a, b, x, y);
    return (x + b) % b;
}

由于写的只是题解,所以只说明了功能,并未讲解原理,如果有对逆元的证明感兴趣的读者,这里推荐一篇乘法逆元的博客

最后,AC代码如下

#include <iostream>
using namespace std;
const int max_n = 1e6;
const int max_k = max_n;
const long long mod_num = 1e9 + 7;

int n, k;
long long factorial[max_n + 1];

long long pow(long long x, long long y, long long p) {//快速幂
	long long ret = 1;
	while (y) {
		if (y & 1) ret = (ret * x) % p;
		x = (x * x) % p;
		y >>= 1;
	}
	return ret;
}

long long inv(long long x, long long p) {//mod p意义下,x的逆元
	return pow(x, p - 2, p);
}

long long cmd(long long m, long long n) {
	long long num1 = factorial[n];
	long long num2 = factorial[m];
	long long inv2 = inv(num2, mod_num);
	long long num3 = factorial[n - m];
	long long inv3 = inv(num3, mod_num);
	return ((num1 * inv2) % mod_num) * inv3 % mod_num;
}

int main() {
	factorial[0] = 1;
	for (int i = 1; i <= max_n; i++) factorial[i] = factorial[i - 1] * i % mod_num;

	cin >> n >> k;
	int max_ones = (n + k) / (k + 1);
	long long ans = 1;
	for (int i = 1; i <= max_ones; i++) {
		int left = n - (i - 1) * k + 1;
		ans = (ans + cmd((i + 1) - 1, left - 1)) % mod_num;
	}
	cout << ans << endl;
	return 0;
}

T10 [Daimayuan]整除光棍(Python,高精度)

这里所谓的“光棍”,并不是指单身汪啦~ 说的是全部由 1 1 1 组成的数字,比如 1 1 1 11 11 11 111 111 111 1111 1111 1111 等。传说任何一个光棍都能被一个不以 5 5 5 结尾的奇数整除。比如, 111111 111111 111111 就可以被 13 13 13 整除。 现在,你的程序要读入一个整数 x x x ,这个整数一定是奇数并且不以 5 5 5 结尾。然后,经过计算,输出两个数字:第一个数字 s s s,表示 x x x 乘以 s s s 是一个光棍,第二个数字 n n n是这个光棍的位数。这样的解当然不是唯一的,题目要求你输出最小的解。

提示:一个显然的办法是逐渐增加光棍的位数,直到可以整除 x x x 为止。但难点在于, s s s 可能是个非常大的数 —— 比如,程序输入 31 31 31 ,那么就输出 3584229390681 3584229390681 3584229390681 15 15 15 ,因为 31 31 31 乘以 3584229390681 3584229390681 3584229390681 的结果是 111111111111111 111111111111111 111111111111111 ,一共 15 15 15 1 1 1

输入格式

输入在一行中给出一个不以 5 5 5 结尾的正奇数 x x x x < 1000 x<1000 x<1000)。

输出格式

在一行中输出相应的最小的 s s s n n n,其间以 1 1 1 个空格分隔。

样例输入
31
样例输出
3584229390681 15
解题思路

其实题目已经教你怎么做这道题了,你需要做的就是写一个高精度取模和除法

但是总有人连这个都不想写,于是采用了 P y t h o n Python Python自带的高精度

*注:求 s s s的时候要采用整除//操作,除法/操作会自动转换结果为浮点数,因此丢失精度

AC代码如下

len = 1
num = 1
x = int(input())
while num % x != 0:
    len += 1
    num = num * 10 + 1
print(num // x, len)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

WitheredSakura_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值