北京交通大学-2024算法设计与分析-期中考试

介绍

这是2024年北京交通大学研究生秋季算法设计与分析期中考试的题目。我准备这门课考试时苦于找不到往年题或找错往年题,不仅会浪费大量时间,而且不能估计这门课考试的难度。为了让学习学妹们应对这些问题,这里给出题目、思路以及代码。

A.简单数学

题目描述

对于一个给定的正整数n,求有多少对正整数x,y满足公式 n = 2 x + y 2 n=2x+y^2 n=2x+y2

输入格式

一个正整数n(1≤n≤ 1 0 18 10^{18} 1018)。

输出格式

在单独的行中输出结果,即满足条件数对的个数。

输入样例

10

输出样例

1

思路

这是一道思维题,数据范围是 1 0 18 10^{18} 1018,不能通过枚举。如何解决呢。关键思路为考虑奇偶性,观察发现: 2 x 一定是偶数 2x一定是偶数 2x一定是偶数。分类讨论,当n是偶数时, y 2 一定是偶数 y^2一定是偶数 y2一定是偶数,答案可以转化为小于n的平方数有多少是正偶数,即 max ⁡ y y \max\limits_{y} y ymaxy 满足y是正整数且 y 2 是偶数 y^2是偶数 y2是偶数 y 2 < n y^2<n y2<n,进而转化为小于 ⌊ n ⌋ \lfloor\sqrt n\rfloor n 的正偶数有多少个,即 max ⁡ y y \max\limits_{y} y ymaxy 满足y是正整数且 y 是偶数 y是偶数 y是偶数 y < ⌊ n ⌋ y<\lfloor\sqrt n\rfloor y<n 。当n是奇数时同理。实现的时候记得开long long。

代码(c++)

#include <bits/stdc++.h>
//#define int long long

using namespace std;
typedef long long ll;

inline ll read() {
	ll x = 0, f = 1;
	char ch = getchar();
	while (ch < '0' || ch > '9') {
		if (ch == '-')
			f = -1;
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9' )
		x = (x << 1) + (x << 3) + (ch ^ 48), ch = getchar();
	return x * f;
}

int main() {
	ll n;
	cin >> n;

	ll sq = sqrt(n);
//	cout << sq << endl;
	if (sq * sq == n)
		sq--;

	int res = 0;
	if (n % 2 == 0) {
		ll even = sq / 2;
		cout << even << endl;
	} else {
		ll odd = (sq + 1) / 2;
		cout << odd << endl;
	}

	return 0;
}

B.被整蛊的序列

题目描述

小Y和小H是好朋友,这一天趁着小H出去了,小Y打算整蛊他。他在小H的桌上发现了一个长度为 m的正整数序列 a 1 , a 2 , ⋯ , a m ( 1 ≤ a i ≤ n ) a_1,a_2,⋯,a_m (1≤a_i≤n) a1,a2,,am(1ain) ;接着,小Y选择了一个长度为 n的正整数序列 f 1 , f 2 , ⋯ , f n f_1,f_2,⋯,f_n f1,f2,,fn;并根据公式 b i = f a i b_i=f_{ai} bi=fai生成了一正整数序列 b 1 , b 2 , ⋯ , b m b_1,b_2,⋯,b_m b1,b2,,bm替换了原先的序列 a。
小H回来发现序列变了,但是“好心”的小Y给他留下了序列 f 。现在小H希望你根据现有的序列 f和 b 帮他恢复原始序列 a 。

输入格式

第一行为2个正整数, n和 m (1≤n,m≤ 1 0 5 10^5 105),分别表示序列 f 的长度和序列 b的长度;第二行为 n个正整数 f 1 , f 2 , ⋯ , f n ( 1 ≤ f i ≤ n ) f_1,f_2,⋯,f_n(1≤f_i≤n) f1,f2,,fn(1fin)
第二行为 m个正整数 b 1 , b 2 , ⋯ , b m ( 1 ≤ b i ≤ n ) b_1,b_2,⋯,b_m (1≤b_i≤n) b1,b2,,bm(1bin)

输出格式

如果存在唯一的一个序列 a 1 , a 2 , ⋯ , a m a_1,a_2,⋯,a_m a1,a2,,am满足对于 1≤i≤m中的所有 i,都有 b i = f a i b_i=f_{ai} bi=fai,输出"Possible",而后输出恢复得到的原始序列 a 1 , a 2 , ⋯ , a m a_1,a_2,⋯,a_m a1,a2,,am,中间用空格分隔;
如果因为小Y的计算错误导致不存在满足要求的序列,则输出"Impossible";
如果存在多个满足要求的序列,则输出"Ambiguity"。

输入样例1

3 3
1 1 1
1 1 1

输出样例1

Ambiguity

输入样例2

3 3
1 2 1
3 3 3

输出样例2

Impossible

输入样例3

3 3
3 2 1
1 2 3

输出样例3

Possible
3 2 1

思路

这是一道枚举题。首先知道,如果小Y没有出错的话, b i 一定是 f 里面的数 b_i 一定是 f 里面的数 bi一定是f里面的数。那么逆否命题,如果 b i b_i bi不是f里面的数,则小Y出错,应输出Impossible。Ambiguity的情况如何确定呢?输出的序列其实是 b i 在 f 中 b_i在f中 bif的位置,有多个满足要求序列代表着存在某个 b i 在 f b_i在f bif中出现了多次。此时输出Ambiguity。具体实现上可以通过一个数组tong[ f i f_i fi]记录 f i f_i fi中每个数字出现的位置,同时在遍历过程中判断是否出现重复数字,但是 f i f_i fi中出现重复数字不意味着就是Ambiguity的情况,还要判断是否在 b b b中出现。为此当某个数字重复出现时,设置tong[]=-2,并判断这种情况的数是否在b中出现。如果存在再输出Ambiguity。至于Possible的答案也可以通过遍历b数组,答案为tong[ b i b_i bi]

代码(c++)

#include <bits/stdc++.h>
//#define int long long

using namespace std;
typedef long long ll;

inline ll read() {
	ll x = 0, f = 1;
	char ch = getchar();
	while (ch < '0' || ch > '9') {
		if (ch == '-')
			f = -1;
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9' )
		x = (x << 1) + (x << 3) + (ch ^ 48), ch = getchar();
	return x * f;
}
const int N = 200010;
int b[N], f[N];
int tong[N];
int res[N];
int n, m;

int main() {
	cin >> n >> m;
	for (int i = 1 ; i <= n ; i ++)
		cin >> f[i];
	for (int i = 1 ; i <= m ; i ++)
		cin >> b[i];

	memset(tong, -1, sizeof tong);
	bool flag = false;
	for (int i = 1 ; i <= n ; i ++) {
		if (tong[f[i]] != -1) {
			flag = true;
			tong[f[i]] = -2;
			continue;
		}
		tong[f[i]] = i;
	}
//	cout << tong[1] << endl;
	bool flag3 = false;
	bool flag2 = false;
	for (int i = 1 ; i <= m ; i ++) {
		if (tong[b[i]] == -1) {
			flag2 = true;
			break;
		}
		if (tong[b[i]] == -2) {
			flag3 = true;
		}
		res[i] = tong[b[i]];
	}
	if (flag2) {
		puts("Impossible");
		return 0;
	}
	if (flag && flag3) {
		puts("Ambiguity");
		return 0;
	}
	puts("Possible");
	bool flagg = false;
	for (int i = 1 ; i <= m ; i ++) {
		if (flagg)
			cout << ' ';
		flagg = true;
		cout << res[i] ;
	}
	cout << endl;
	return 0;
}

C.低级祖玛

题目描述

小F最近回想起了小时候特别喜欢玩的祖玛游戏,现在他想到了一个这样的游戏;

游戏开始会有 N个球,每个球有一个非负整数分数 a i a_i ai,现在你将操作金蟾,消除第 i个球,消除该球得到一个分数 a i − 1 × a i × a i + 1 a_{i−1}×a_i×a_{i+1} ai1×ai×ai+1(也就是该球与该球相邻的两个球的分数乘积)。

当你消除最左端或最右端的球时,可以理解为旁边的空气为一个1分的球(用消除最左侧的1号球为例,消除该球得到的分数为 1 × a 1 × a 2 1×a_1×a_2 1×a1×a2 )。

和祖玛游戏一样,当你消除这个球时,原来的 i−1号球和 i+1号球将会变为相邻的球。当所有球消完后,你将得到最后的分数。

现在小F希望你帮他获得最大的分数。

输入格式

第一行为一个正整数 N(1≤N≤300)表示球的总数,第二行为 N个 a 1 , a 2 , ⋯ , a N ( 0 ≤ a i ≤ 100 ) a_1,a_2,⋯,a_N(0≤a_i≤100) a1,a2,,aN(0ai100) ,表示每个球对应的分数。

输出格式

输出一个数字,表示能够获得的最大分数。

输入样例

4
3 1 5 8

输出样例

167

思路

这是一道dp题。考试时自己定义状态没做出来,最后发现这题可以转化为一道做过的经典dp问题—矩阵连乘问题,可以去百度一下,那个题是求最小计算次数,这题是求最大计算次数 。

代码(c++)

#include <bits/stdc++.h>
#define int long long
#define x first
#define y second

using namespace std;
typedef long long ll;

inline ll read() {
	ll x = 0, f = 1;
	char ch = getchar();
	while (ch < '0' || ch > '9') {
		if (ch == '-')
			f = -1;
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9' )
		x = (x << 1) + (x << 3) + (ch ^ 48), ch = getchar();
	return x * f;
}
const int N = 310;

int n;
int a[N];
int f[N][N];

signed main() {
	cin >> n;
	for (int i = 1 ; i <= n ; i ++)
		cin >> a[i];
	a[n + 1] = 1;
	vector<pair<int, int>> v;
	v.push_back(make_pair(1, 1));
	for (int i = 1 ; i <= n ; i ++) {
		if (i == 1)
			v.push_back(make_pair(1, a[i]));
		v.push_back(make_pair(a[i], a[i + 1]));
	}
//	for (int i = 1 ; i <= n + 1 ; i ++)
//		cout << v[i].x << ' ' << v[i].y << endl;

	for (int len = 2 ; len <= n + 1 ; len ++) {
		for (int i = 1 ; i + len - 1 <= n + 1 ; i ++) {
			int j = i + len - 1;
			if (len == 2)
				f[i][j] = v[i].x * v[i].y * v[i + 1].y;
			else
				for (int k = i  ; k < j ; k ++) {
					f[i][j] = max(f[i][j], f[i][k] + f[k + 1][j] + v[i].x * v[k].y * v[j].y);
				}
		}
	}
	cout << f[1][n + 1];

}

D.烘焙大师

题目描述

一天,著名的烘焙大师Alex接到了一项紧急任务——在一夜之间烤制 n个蛋糕。由于工作了一整天,Alex感到非常疲惫,他的工作方式是:首先烤 v个蛋糕,然后休息一会儿,接着烤 ⌊v/k⌋个蛋糕,再休息,然后烤 ⌊ v / k 2 v/k^2 v/k2⌋个蛋糕,以此类推: v,⌊v/k⌋,⌊ v / k 2 v/k^2 v/k2⌋,…,。当当前的 ⌊ v / k i v/k^i v/ki⌋的值等于0时,Alex会立即睡着,直到早上醒来时,蛋糕应该已经全部烤好。Alex想知道,最小的 v值是多少,才能让他在睡觉之前烤出不少于n个蛋糕。
ps: ⌊⋅⌋ 运算表示对数字向下取整

输入格式

输入由两个整数 n和 k组成,空格分离——分别表示要烤制蛋糕的数量和生产力降低系数, 1 ≤ n ≤ 1 0 9 1≤n≤10^9 1n109,2≤k≤10.

输出格式

输出一个整数——允许Alex在一夜之间烤出 n个蛋糕的最小 v值。

输入样例

7 2

输出样例

4

思路

像这种求最小的满足某个条件的数,不得不让人想起二分。检查是否满足二分条件即单调性。即大的v可以满足条件时,小的v是否满足。当然能满足,因为当v大的时候肯定比小的时候切的蛋糕多。直接二分。

代码(c++)

#include <bits/stdc++.h>
//#define int long long

using namespace std;
typedef long long ll;

inline ll read() {
	ll x = 0, f = 1;
	char ch = getchar();
	while (ch < '0' || ch > '9') {
		if (ch == '-')
			f = -1;
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9' )
		x = (x << 1) + (x << 3) + (ch ^ 48), ch = getchar();
	return x * f;
}
const int N = 100010;
int n, k;

bool check(int v) {
	int sum = 0;
	int p = 0;
	while (v / (pow(k, p)) != 0) {
		sum += v / (pow(k, p));
		p++;
	}
	if (sum >= n)
		return true;
	return false;
}

int main() {
	cin >> n >> k;

	int l = 1, r = n;

	while (l < r) {
		int mid = l + r >> 1;
		if (check(mid))
			r = mid;
		else
			l = mid + 1;
	}
	cout << r << endl;
}

E.特殊密码

题目描述

在一个网络安全公司,安全专家正在设计一个特殊的密码生成器。这个密码的构成遵循以下规则,请注意数字位置下标从0开始:
偶数位置的字符必须是十进制一位偶数 (0,2,4,6,8)。
奇数位置的字符必须是十进制一位质数 (2,3,5,7)。
安全专家决定生成长度为 n的密码。请你计算所有满足条件的密码总数。由于答案可能非常庞大,请将结果对 1 0 9 + 7 10^9+7 109+7取余后返回。

输入格式

一个正整数 n(1≤n≤ 1 0 15 10^{15} 1015),表示密码的长度。

输出格式

返回一个数,表示长度为 n的密码总数对 1 0 9 + 7 10^9+7 109+7取余的结果。

输入样例

1

输出样例

5

思路

用快速幂模板。不会快速幂的可以去搜一下。快速幂是用来求 a b % p a^b\%p ab%p,并且可以以在log(b)的时间复杂度求的。记得开long long。

代码(c++)

#include <bits/stdc++.h>
#define int long long

using namespace std;
typedef long long ll;

inline ll read() {
	ll x = 0, f = 1;
	char ch = getchar();
	while (ch < '0' || ch > '9') {
		if (ch == '-')
			f = -1;
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9' )
		x = (x << 1) + (x << 3) + (ch ^ 48), ch = getchar();
	return x * f;
}
const int N = 200010, mod = 1000000007;


ll qmi(int a, int b, int p) {
	int res = 1;
	int t = a;
	while (b) {
		if (b & 1)
			res = res * t % p;
		t = t * t % p;
		b >>= 1;
	}
	return res;
}

signed main() {
	ll n;
	cin >> n;
	ll even = (n + 1) / 2;
	ll odd = n / 2;
	ll res = qmi(5, even, mod) * qmi(4, odd, mod) % mod;

	cout << res << endl;


}

F.志愿者咨询服务

题目描述

在一场大型会议中,志愿者沿着一条长度为l的街道提供咨询服务。我们将街道视为一个坐标系统,其中起点为0,终点为l。第i个志愿者位于坐标ai。每个志愿者能够服务的范围为距离d,即在ai−d到ai+d的区间内都能提供帮助。
现在会议主办方希望知道,为了确保每位参会者在街道上都能找到志愿者进行咨询,志愿者的服务半径d至少需要多大。

输入格式

第一行包含两个整数n和l(1≤n≤1000,1≤l≤ 1 0 9 10^9 109),分别表示志愿者的数量和街道的长度。
第二行包含n个整数ai(0≤ai≤l),表示第i个志愿者的坐标。

输出格式

输出所需的最小服务半径d,以确保整个街道都能被志愿者覆盖。答案的绝对或相对误差不得超过 1 0 − 4 10^{−4} 104.

输入样例

5 10
0 10 2 6 4

输出样例

2.00000

思路

这是一个模拟题。考试时做麻烦了用二分做的。对a[n]排序,记录排序后a数组相邻元素的最大距离设为maxn,将maxn/=2,因为相邻两个人之间的距离可以一个人负责一半的。还有最边上两个人到两端的两段需要有人负责,当然是a[1]和a[n]负责,于是答案res=max(maxn,a[1]-0,len-a[n])
这题的条件有点唬人,让人感觉要考虑精度问题。其实通过思考发现答案要不是整数要不小数点后一定是.5。

模拟代码(c++)

#include <bits/stdc++.h>
#define int long long

using namespace std;
typedef long long ll;

inline ll read() {
	ll x = 0, f = 1;
	char ch = getchar();
	while (ch < '0' || ch > '9') {
		if (ch == '-')
			f = -1;
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9' )
		x = (x << 1) + (x << 3) + (ch ^ 48), ch = getchar();
	return x * f;
}
const int N = 1010, mod = 1000000007;
const double eps = 1e-6;
int a[N];
pair<int, int> p[N];
int n, rlimit;

bool check(int mid) {
	int llimit = 0;
	for (int i = 1 ; i <= n ; i ++) {
		int l = a[i] - mid, r = a[i] + mid;
		if (l > llimit)
			return false;
		llimit = r;
	}
	if (llimit >= rlimit)
		return true;
	return false;
}

signed main() {
	cin >> n >> rlimit;
	for (int i = 1 ; i <= n ; i ++) {
		cin >> a[i];
//		cout<<a[i]<<' ';
	}
//	cout<<endl;
	

	a[0]=0,a[n+1]=rlimit;
	sort(a , a + n + 2);
	double maxn = 0;
	for(int i = 0 ; i < n ; i ++){
		maxn= max(maxn,1.0*a[i+1]-a[i]);
	}
	maxn/=2;
	maxn = max(maxn,1.0*a[1]-0);
	maxn = max(maxn,1.0*rlimit-a[n]);
	printf("%.5lf",maxn); 
}

二分代码(c++)

#include <bits/stdc++.h>
#define int long long

using namespace std;
typedef long long ll;

inline ll read() {
	ll x = 0, f = 1;
	char ch = getchar();
	while (ch < '0' || ch > '9') {
		if (ch == '-')
			f = -1;
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9' )
		x = (x << 1) + (x << 3) + (ch ^ 48), ch = getchar();
	return x * f;
}
const int N = 1010, mod = 1000000007;
const double eps = 1e-6;
int a[N];
pair<int, int> p[N];
int n, rlimit;

bool check(int mid) {
	int llimit = 0;
	for (int i = 1 ; i <= n ; i ++) {
		int l = a[i] - mid, r = a[i] + mid;
		if (l > llimit)
			return false;
		llimit = r;
	}
	if (llimit >= rlimit)
		return true;
	return false;
}

signed main() {
	cin >> n >> rlimit;
	rlimit *= 2;
	for (int i = 1 ; i <= n ; i ++) {
		cin >> a[i];
		a[i] *= 2;
//		cout<<a[i]<<' ';
	}
//	cout<<endl;
	sort(a + 1, a + n + 1);

	int l = 1, r = rlimit;
	while (l < r) {
		int mid = l + r >> 1;
//		cout << mid << endl;
		if (check(mid))
			r = mid;
		else
			l = mid + 1;
	}
	printf("%.5lf", 1.0 * l / 2);
}

总结

题目类型分为思维题、模拟题、二分题、模版题、dp题,前四种题做过一些算法题的同学应该问题不大。dp题也是要多练的。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值