北京交通大学-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(1≤ai≤n) ;接着,小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(1≤fi≤n);
第二行为 m个正整数
b
1
,
b
2
,
⋯
,
b
m
(
1
≤
b
i
≤
n
)
b_1,b_2,⋯,b_m (1≤b_i≤n)
b1,b2,⋯,bm(1≤bi≤n);
输出格式
如果存在唯一的一个序列
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中 bi在f中的位置,有多个满足要求序列代表着存在某个 b i 在 f b_i在f bi在f中出现了多次。此时输出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} ai−1×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(0≤ai≤100) ,表示每个球对应的分数。
输出格式
输出一个数字,表示能够获得的最大分数。
输入样例
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 1≤n≤109,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} 10−4.
输入样例
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题也是要多练的。