复旦巨喜欢考动规,建议好好准备
给定五个 0~9 范围内的整数 a1, a2, a3, a4, a5。如果能从五个整数中选出三个并且这三个整数的和为10 的倍数(包括 0),那么这五个整数的权值即为剩下两个没被选出来的整数的和对 10 取余的结果,显然如果有多个三元组满⾜和是 10 的倍数,剩下两个数之和对 10 取余的结果都是相同的;如果
选不出这样三个整数,则这五个整数的权值为 -1。
现在给定 T 组数据,每组数据包含五个 0~9 范围内的整数,分别求这 T 组数据中五个整数的权值。
【输⼊格式】 第⼀⾏⼀个整数 T (1<=T<=1000),表⽰数据组数。 接下来 T ⾏,每⾏ 5 个 0~9 的整数,表⽰⼀组数据。
【输出格式】输出 T ⾏,每⾏⼀个整数,表⽰每组数据中五个整数的权值。
【样例输⼊】
4
1 0 0 1 0
1 0 0 8 6
3 4 5 6 7
4 5 6 7 8【样例输出】
2
-1
-1
0
【解释】
在第⼀组(1 0 0 1 0)中,三元组 0 0 0 的和为 0,是 10 的倍数,剩余的 1 1 之和为 2,对 10 取余为2。
在第⼆组中,不存在任何⼀个三元祖只和为 10 的倍数。
在第四组中,三元组 5 7 8 的和为 20,是 10 的倍数,剩余的 4 6 只和为 10,对 10取余为 0。
在第五组中,三元组 0 3 7 和三元组 0 4 6 的和都是 10,是 10 的倍数,但是根据简单的数论可知,如果存在多个三元组满⾜情况,那么剩余数字的结果之和对 10 取余是相等的,在本例中和为 10,对 10取余为 0。
基本思路:
其实就是判断是否有三数之和为10的倍数,而且确定总共只有五个数,那么可以用fou循环来解决,但是为了代码美观而且看起来牛掰点,还是使用了回溯法,二者的时间复杂度和空间复杂度是相同的。最后如果三数之和为10的倍数,那么只要五数之和减去三数之和就可以了。
时间复杂度:O(n)
空间复杂度:O(1)
回溯法:使用visited数组来记录每个元素的是否被访问过,每次确定一个元素,然后递归确定其他元素
代码:
#include
#include
#include
using namespace std;
//回溯法得到其中三数之和,若没有10的倍数,返回-1
int judge(int sum,vector<int>& visited,vector<int>D,int num) {
if (num > 3) return -1;
if (num == 3 && sum % 10 == 0) return sum;
for (int i = 0; i < D.size(); i++) {
if (visited[i] == 0) {
visited[i] = 1;
int f = judge(sum + D[i], visited, D, num + 1);
if (f != -1) return f;
visited[i] = 0;
}
}
return -1;
}
int main()
{
int T;
cin >> T;
int N = 5;
vector<vector<int>>data(T, vector<int>(N));
for (int i = 0; i < T; i++) {
for (int j = 0; j < N; j++) {
cin >> data[i][j];
}
}
vector<int>result;
vector<int> visited(N, 0); //记录已被访问过的数
for (int i = 0; i < T; i++) {
int sum = 0, num = 0;
fill(visited.begin(), visited.end(), 0);
int flag = judge(sum, visited, data[i], num);
if (flag != -1) { //存在三数之和为10的倍数
int S = 0;
for (int j = 0; j < N; j++) S += data[i][j];
result.push_back((S - flag) % 10); //总和减去三数之和就是剩余两数之和
}
else result.push_back(flag);
}
for (int i = 0; i < result.size(); i++) cout << result[i] << endl;
}
给定 n 个整数 a1, a2, …, an 和⼀个 d,你需要选出若⼲个整数,使得将这些整数从⼩到⼤排好序之后,任意两个相邻的数之差都不⼩于给定的 d,问最多能选多少个数出来。
【输⼊格式】 第⼀⾏两个整数 n,d (1<=n<=10^5, 0<=d<=10^9),分别表⽰整数个数和相邻整数差的下界。 第⼆⾏ n
个整数 a1, a2, …, an (1<=ai<=10^9, 1<=i<=n),表⽰给定的 n 个整数。
【输出格式】 仅⼀⾏⼀个整数,表⽰答案。
【样例输⼊】
6 2
1 4 2 8 5 7【样例输出】
3
【解释】
注意,选出的数在排序后,相邻两数之差不⼩于给定值。
B. 打地⿏
⽐如,对于给定值 2,[1 4 7] 是⼀个满⾜条件的选择⽅案,但是[1 4 5] 却不是,因为 5 - 4 = 1 < 2。
在本样例中,[1 4 7],[1 4 8],[1 5 7],[1 5 8],[2 4 7],[2 4 8] 都是满⾜要求的选择⽅案,但是⽆论如何都没有办法得到⼀个选出 4 个数且满⾜条件的⽅案,所以本样例的答案为 3。
基本思路:
有两种方法,一种是动规,一种是贪心。这里我使用的是贪心
首先递增排序,data[i]代表第 i 个数,d代表差,那么:
1、如果data[j]-data[i]>=d,由于数据增序排列,那么data[k] (j2、如果data[j]-data[i] 时间复杂度:O(nlogn)
空间复杂度:O(1)
代码
#include
#include
#include
using namespace std;
int main()
{
int n, d;
cin >> n >> d;
vector<int>data(n);
for (int i = 0; i < data.size(); i++) cin >> data[i];
sort(data.begin(), data.end()); //从小到大排序
int num = 0;
if (n < 2) { //只有一个整数
cout << num << endl;
return 0;
}
for (int i = 0, j = 1; j < data.size();) {
if (data[j] - data[i] >= d) {
num++;
i = j;
j++;
}
else j++;
}
if (num == 0) cout << num << endl; //没有符合条件的数
else cout << num + 1 << endl; //由于每次只将较小的数加入结果集,因此最后还需将最后的大数加入结果集
return 0;
}
下课了,有 n 位同学陆续赶到⻝堂进⾏排队打饭,其中第 i 位同学的到达时间为 ai,打饭耗时为 ti,等待时间上限为 bi,即如果其在第 ai+bi 秒的时刻仍然没有轮到他开始打饭,那么他将离开打饭队列,另寻吃饭的地⽅。问每位同学的开始打饭时间,或者指出其提前离开了队伍(如果这样则输出 -1)。
【输⼊格式】 第⼀⾏⼀个整数 n (1<=n<=10^5),表⽰来打饭的同学数量。 接下来 n ⾏,每⾏三个整数 ai,ti,bi (1<=ai,ti,bi<=10^9, 1<=i<=n),分别表⽰每位同学的到达时间、打 饭耗时、等待时间上限。 保证 a1
【输出格式】 ⼀⾏ n 个整数,表⽰每位同学的开始打饭时间或者 -1(如果该同学提前离开了队伍)。
【样例输⼊】
4
1 3 3
2 2 2
3 9 1
4 3 2
【样例输出】
1 4 -1 6
【解释】
C. 排队打饭
第⼀个同学在 1 时刻到达队列,需要 3 个单位时间才能打好饭(也就是说如果在 1 时刻开始打饭,那么将在 1 + 3 = 4 时刻打好饭离开),最⻓等待时间为 3 个单位时间(也就说如果在到达队列之后的 3单位时间后还没开始给他打饭,他就忍耐不了离开了)。
在本样例中,
1 时刻:第⼀个同学在 1 时刻到达队列,同时开始了打饭操作(对应输出的第⼀个值为 1)。
2 时刻:在 2 时刻,第⼆个同学加⼊了队列,给第⼆个同学打饭需要 2 个单位时间,但是如果在等待了 2 个单位时间没给第⼆个同学打饭的话,第⼆个同学将离开。
3 时刻:在 3 时刻,第三个同学加⼊了队列,给第三个同学打饭需要 9 个单位时间,但是如果在等待了 1 个单位时间没给第三个同学打饭的话,第三个同学将离开,换句话说,如果在 3 (到达时刻) + 1(可等待时间⻓度)= 4 时刻还没给第三个同学打饭,那么第三个同学将离开。
4 时刻:第⼀个同学在时刻 4 打完饭离开,同时队列⾥的第⼆个同学开始打饭(对应输出的第⼆个值为 4),此时第三个同学没有达到饭,所以第三个同学就在时刻 4 离开了队伍(对应输出的第三个值为 -1)。同时,在时刻 4,第四个同学也加⼊了队列,第四个同学最⻓等待到 4(到达时刻)+ 2 (可等待时间⻓度)= 6 时刻。
5 时刻:5 时刻还在给第⼆个同学打饭,第四个同学还在队列⾥⾯排队。
6 时刻:6 时刻,第⼆个同学打饭完成,同时第四个同学开始打饭(对应输出的第四个值为 6)。
根据上⾯描述的过程,输出为 1 4 -1 6。
主要思路
使用endtime来记录上一个学生的打饭的结束时间,那么当前学生的开始时间如果大于等于endtime,就可以直接开始;否则的话,当前学生的开始时间为endtime,同时计算等待时间,判断是否大于最长等待时限。
时间复杂度:由于默认到达时间递增,所以只需按到达时间遍历一遍就够了,位O(n);
空间复杂度:只需要一个用来记录上一位学生的打饭结束时间的变量,为O(1)
代码
#include
#include
#include
using namespace std;
int main()
{
int n;
cin >> n;
vector<vector<long long int>>data(n, vector<long long int>(3));
for (int i = 0; i < data.size(); i++){
for (int j = 0; j < data[i].size(); j++) {
cin >> data[i][j];
}
}
long long int endtime = 0; //记录上一个学生打饭结束的时间
vector<long long int>result;
for (int i = 0; i < data.size(); i++) {
if (data[i][0] >= endtime) { //第i位学生达到时无人打饭,可以直接开始打饭
result.push_back(data[i][0]);
endtime = data[i][0] + data[i][1];
}
else { //第i位学生正好有人打饭,那么第i位学生的等待时间=上一位学生的结束时间-第i位学生的开始时间
if (endtime - data[i][0] > data[i][2]) result.push_back(-1);
else {
result.push_back(endtime);
endtime = endtime + data[i][1];
}
}
}
for (int i = 0; i < result.size(); i++) cout << result[i] << ' ';
return 0;
}
给定⼀个 1~n 的排列 P,即⻓度为 n,且 1~n 中所有数字都恰好出现⼀次的序列。现在按顺序将排列中的元素⼀⼀插⼊到初始为空的⼆叉搜索树中(左小右大),问最后每个节点的⽗亲节点的元素是什么。特别地,根节点的⽗亲节点元素视为 0。
【输⼊格式】
第⼀⾏⼀个整数 n (1<=n<=10^5),表⽰排列 P 中的元素个数。
第⼆⾏ n 个整数 p1, p2, …, pn (1<=pi<=n, 1<=i<=n),表⽰给定的排列。
【输出格式】 ⼀⾏ n 个整数,其中第 i 个整数 ai 表⽰元素 i 对应节点的⽗亲节点的元素。特别地,根节点的⽗亲节 点元素视为 0。
【样例输⼊】
5
2 3 5 1 4
【样例输出】
2 0 2 5 3
【样例解释】
最后建出来的⼆叉搜索树如下:
1 的⽗亲为 2,2 为根结点,所以⽗亲为 0,3 的⽗亲为 2,4 的⽗亲为 5,5 的⽗亲为 3。
主要思路
这是最难的一题了,做是能做出来,但是基本都超时,这里介绍两种方法:
第一种:最容易想到的方法,不就是求搜索二叉树的父节点嘛,只要建一棵树出来不就得了。
时间复杂度:O(n2)
空间复杂度:O(n)
代码
#include
#include
#include
#include
using namespace std;
typedef struct node {
int v;
node* left;
node* right;
}node;
int main()
{
//边建树边查找,n2
int n;
cin >> n;
vector<int>data(n + 1);
for (int i = 1; i < data.size(); i++) cin >> data[i];
vector<int>result(n + 1);
node* T = new node;
T->v = 0; T->left = NULL; T->right = NULL;
//边遍历边构造二叉搜索树,在找到插入位置时,同时保存父节点
for (int i = 1; i < data.size(); i++) {
node* p = T;
while (true) {
if (data[i] > p->v) {
if (p->right) p = p->right;
else {
node* q = new node;
q->v = data[i]; q->left = NULL; q->right = NULL;
p->right = q;
break;
}
}
else {
if (p->left) p = p->left;
else {
node* q = new node;
q->v = data[i]; q->left = NULL; q->right = NULL;
p->left = q;
break;
}
}
}
result[data[i]] = p->v;
}
for (int i = 1; i < result.size(); i++) cout << result[i] << ' ';
return 0;
}
第二种:
使用一个 f 数组来记录每一个节点的父节点,使用一个map来记录每一个节点的层次(初始化0节点的层次为0),使用mx来记录当前树中的最大值:
1、新加入的节点的权值若大于mx,那么该节点的父节点必定是mx,并且更新mx,同时将该节点的层次加入map;
2、新加入的节点的权值若小于mx,查找map中第一个大于该节点权值的键next以及最后一个小于该节点的键pre(因为map会自己按键值排列,且next和pre比相邻,因此用二分查找)
由于二叉搜索树的中序遍历必定是增序的,因此该节点的中序遍历位置必定pre和next之间。
因此如果next的层次大于pre(即next是pre的右子树的最左下节点),若想中序递增,当前节点必定是next的左孩子,因此将当前节点加入map,其层次为next的层次加1;
如果next的层次小于pre(pre是next的左子树的最右下节点),若想中序递增,那么当前节点必是pre的右孩子,因此当前的节点的层次为pre的层次加1;
时间复杂度:查找带插入位置使用二分查找法,因此为O(nlogn);
空间复杂度:O(n)
代码
#include
#include
#include
#include
using namespace std;
int main(){
int n;
cin >> n;
vector<int>father(n + 1);
map<int, int>dict;
dict[0] = 0;
int maxn = 0;
for (int i = 0; i < n; i++) {
int t;
cin >> t;
if (t > maxn) {
father[t] = maxn;
dict[t] = dict[maxn] + 1;
maxn = t;
}
else {
map<int, int>::iterator small = dict.upper_bound(t);
map<int, int>::iterator big = small--;
if (big->second > small->second) {
father[t] = big->first;
dict[t] = big->second + 1;
}
else {
father[t] = small->first;
dict[t] = small->second + 1;
}
}
}
for (int i = 1; i < father.size(); i++) cout << father[i] << ' ';
}
给定⼀个⻓为 n 的序列 A,其中序列中的元素都是 0~9 之间的整数,对于⼀个⻓度同样为 n 整数序列B,定义其权值为 |A_i-B_i| (1<=i<=n) 之和加上 (B_j-B_j+1)^2 (1<=j 【输⼊格式】 第⼀⾏⼀个整数 n (1<=n<=10^5),表⽰序列 A 的⻓度。 第⼆⾏ n 个整数 a1, a2, …, an (0<=ai<=9, 1<=i<=n),表⽰序列 A 中的元素。 【输出格式】仅⼀⾏⼀个整数,表⽰答案。 【样例输⼊】 【样例输出】 【解释】 主要思路 二维动规法:dp[i][j]代表处理到第i个数时填j 的当前最小权值和 dp[i][j] = min(dp[i][j], dp[i - 1][k] + abs(A[i] - j) + (j - k)(j -k)); dp[i][j] 代表当前在B[i]填j所得到的当前的最小权值,初始化为INT_MAX; 代码
6
1 4 2 8 5 7
11
A 数组是 [1 4 2 8 5 7]
B 数组可以是 [3 4 4 5 5 6]。
权值为 |A_i - B_i| (1<=i<=n) 之和加上 (B_j - B_j+1)^2 (1<= j
|1 - 3| + |4 - 4| + |2 - 4| + |8 - 5| + |5 - 5| + |7 - 6| = 2 + 0 + 2 + 3 + 0 + 1 = 8
权值第⼆部分(B_j - B_j+1)^2 (1<= j
所以总权值为 8 + 3 = 11。
for (int i = 1; i < A.size(); i++) {
for (int j = 0; j < N; j++) {
for (int k = 0; k < N; k++) {
dp[i][j] = min(dp[i][j], dp[i - 1][k] + abs(A[i] - j) + (j - k)*(j - k));
}
}
}
dp[i - 1][k] + abs(A[i] - j) + (j - k)(j - k):遍历B[i-1]填k(k为0到9)的各个情况,计算当前B[i]填j时与各个k计算的权值和
时间复杂度:O(n) 空间复杂度:O(n)
#include
如有其他相关问题,请联系:[email protected]