复旦大学机试题题解(2017~2021)

前言

尝试把复旦大学历年的机试题做一遍,能做多少是多少了,因为没有数据集所以也不好确认是不是对的~(题目来自王道往届学长学姐回忆以及某位热心的同学整理,如果了解来源后,我一定备注来源)
(目前进度:2021、2020、2019、2018、2017)

2021年机试真题

A 完全二叉树 [水题]

题目描述

给定一颗二叉树,树的每个节点的值为一个正整数。如果从根节点到节点 N 的路径上不存在比节点 N 的值大的节点,那么节点 N 被认为是树上的关键节点。求树上所有的关键节点的个数。请写出程序,并解释解题思路。
例子:
输入:3 1 4 3 null 1 5

输出:4(图中蓝色节点是关键节点)

思路

直接的想法是,先依据题意建立一棵二叉树,使用struct构造一个node类型,由于二叉树可以使用数组来保存,下标i就能表示父子结点关系,故只需要存储结点值以及从根结点到当前结点路径上的最大值(包含当前结点值)。
为了后续查找方便起见,先插入一个空结点,让树结点序号从1开始。为了保持整体结构,空结点也需要插入其中。建树完成后,将每个结点的值与父结点的路径上最大值进行比较,如果小于则更新当前结点的路径上最大值,如果大于等于则cnt++。(cnt表示符合题目条件的结点个数)
后来在写程序过程中,发现由于是逐层遍历的,路径上最大值maxn可以与结点值val合并,故删去了val。

代码

#include<bits/stdc++.h>
using namespace std;

struct node{
    int maxn;//从根结点到当前结点的最大值。初始为各个结点的值
    bool null = false;//判断是否为空结点
};

int main()
{
    int i, cnt=1;
    string s;
    vector<node> v;
    node temp;
    v.push_back(temp);//数组头部先插入一个结点,让根结点的下标为1,方便后续查找父结点。
    while(cin>>s){
        node temp;
        if(s!="null") temp.maxn = stoi(s);
        else temp.null = true;//为了维持树的完整结构,空结点也要插入
        v.push_back(temp);
    }
    for(i=2; i<v.size(); i++){
        if(v[i].null) continue;
        if(v[i].maxn>=v[i/2].maxn) cnt++;
        else v[i].maxn=v[i/2].maxn;
    }
    printf("%d\n", cnt);
    system("pause");
    return 0;
}

B 爬楼梯 [动态规划/矩阵快速幂/数学]

题目描述

训练场上有一个台阶,总共有 n 级。一个运动员可以跳 1 级,也可以跳 2 级。
求运动员有多少种跳法。请写出程序,并解释解题思路。

思路

机试时没多想,直接想法是动态规划。状态转移方程为:dp[i] = dp[i-1]+dp[i-2]。
但在机试结束后查看题解,才发现还有多种思路。一种是将斐波那契数列用矩阵的形式表示,利用矩阵快速幂减少运算;第二种是利用斐波那契的数学公式,直接计算结果。

代码

#include<bits/stdc++.h>
using namespace std;

int main(){
    int n, d, i, cnt=1, last;
    scanf("%d %d", &n, &d);
    vector<int> v(n);
    for(i=0; i<n; i++) scanf("%d", &v[i]);
    sort(v.begin(), v.end());
    last = v[0];
    for(int i=1; i<v.size(); i++){
        if(v[i]-last>d){
            cnt++;
            last = v[i];
        }
    }
    printf("%d\n", cnt);
    return 0;
}

相关链接

leetcode 70 爬楼梯

C 目标和 [动态规划]

题目描述

给定一个非负整数序列 x1, x2, …, xn,可以给每一个整数取负数或者取原值,
求有多少种取法使得这些整数的和等于期望值 E。请写出程序,并解释解题思路。
例子:
输入:非负整数序列为 1 1 1 1 1 3 (期望值 E 为 3)
输出 :5
5 种取法分别为:
-1+1+1+1+1 = 3
1-1+1+1+1 = 3
1+1-1+1+1 = 3
1+1+1-1+1 = 3
1+1+1+1-1 = 3

思路

最直接的想法是递归+剪枝和动态规划……但递归有两个明显问题:1.时间复杂度过高;2.题目并没有给出整数序列的范围(长度范围,数值范围)。所以在注释中写了部分剪枝的东西就没有再做下去。
接着考虑动态规划,与背包问题差不多,转移方程很好想到:dp[i][v[i]+k] = dp[i-1][k]+dp[i-1][v[i]*2+k]。不过考虑到每一步的和可能是负数,而且还是数据范围的问题,在将转移方程写出来后花了很多时间在存储结构的选择上。于是想到建立map类型的数组,利用map进行索引。
(由于题目没有给数据范围,所以我觉得和力扣上的494题还是有所区别的,利用map+dp,其实就是将原本dp矩阵的第二维改为map来索引,从而无视数据范围的问题,这是我目前能想到的最好解法~)

代码

#include<bits/stdc++.h>
using namespace std;

int main()
{
    int E, i, temp;
    vector<int> v;
    scanf("%d", &E);
    while(cin >> temp) v.push_back(temp);
    vector<map<int, int> > dp(v.size());
    map<int, int> tmp;
    dp.push_back(tmp);
    dp[0][v[0]] = dp[0][-v[0]] = 1;
    for(i=1; i<v.size(); i++){
        for(auto j=dp[i-1].begin(); j!=dp[i-1].end(); j++){
            if(dp[i].find(v[i]+j->first)!=dp[i].end()) dp[i][v[i]+j->first] += j->second;
            else dp[i][v[i]+j->first] = j->second;
            if(dp[i].find(j->first-v[i])!=dp[i].end()) dp[i][j->first-v[i]] += j->second;
            else dp[i][j->first-v[i]] = j->second;
        }
    }
    printf("%d\n", dp[v.size()-1][E]);
    system("pause");
    return 0;
}

相关链接

leetcode 494 目标和

2020年机试真题

A 斗牛 [水题]

题目描述

给定五个 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

思路

emmmm没想到什么好方法,也许是我想多了,或许本来就是暴力过的。比最直接的想法三层循环稍微改进了一点点,求出五个数对十取余的值记为mod,那么接下来只要通过二层for循环,找到其中两个数对十取余等于mod就好了~

代码

#include<bits/stdc++.h>
using namespace std;

int main(){
    int n, i, j, k, a[5], mod;
    scanf("%d", &n);
    for(i=0; i<n; i++){
        bool exist = false;
        scanf("%d %d %d %d %d", &a[0], &a[1], &a[2], &a[3], &a[4]);
        mod = (a[0]+a[1]+a[2]+a[3]+a[4])%10;
        for(j=0; j<5; j++){
            if(exist) break;
            for(k=j+1; k<5; k++){
                if((a[j]+a[k])%10==mod){
                    printf("%d\n", mod);
                    exist = true;
                    break;
                }
            }
        }
        if(!exist) printf("-1\n");
    }
    return 0;
}

B 打地鼠 [贪心]

题目描述

给定 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

思路

sort一下后,利用贪心思想,第一个数一定是在最后的序列里的,然后依次查找满足条件的值,并修改最后一次记录的元素值。(我不知道是不是自己想的太简单了)

代码

#include<bits/stdc++.h>
using namespace std;

int main(){
    int n, d, i, cnt=1, last;
    scanf("%d %d", &n, &d);
    vector<int> v(n);
    for(i=0; i<n; i++) scanf("%d", &v[i]);
    sort(v.begin(), v.end());
    last = v[0];
    for(int i=1; i<v.size(); i++){
        if(v[i]-last>d){
            cnt++;
            last = v[i];
        }
    }
    printf("%d\n", cnt);
    return 0;
}

C 排队打饭 [排序(勉强算排序吧)]

题目描述

下课了,有 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<a2<…<an
【输出格式】
⼀行 n 个整数,表示每位同学的开始打饭时间或者 -1(如果该同学提前离开了队伍)。
【样例输⼊】
4
1 3 3
2 2 2
3 9 1
4 3 2
【样例输出】
1 4 -1 6

思路

使用结构体存储学生的到来时间ai、打饭花费时间ti、等待时间bi、开始打饭时间st、打饭结束时间lv,第一个学生一定是能打到饭的,后面的学生需要判断等待是否超时:不超时的话,开始打饭时间就是max(ai, 前一个学生lv),并更新自己的打饭结束时间lv;超时的话,开始打饭时间就是-1,结束打饭时间取前一个学生的打饭结束时间。担心输入的学生数据不是按照到来的时间排序的,所以加了一次sort排序。(还是好简单……)

代码

#include<bits/stdc++.h>
using namespace std;

struct student{
    int ai, ti, bi, st, lv;
};

bool cmp(student a, student b){
    return a.ai<b.ai;
}

int main(){
    int n, i;
    scanf("%d", &n);
    vector<student> v(n);
    for(i=0; i<n; i++) scanf("%d %d %d", &v[i].ai, &v[i].ti, &v[i].bi);
    sort(v.begin(), v.end(), cmp);
    v[0].st = v[0].ai;
    v[0].lv = v[0].st+v[0].ti;
    printf("%d ", v[0].st);
    for(i=1; i<v.size(); i++){
        if(v[i].ai+v[i].bi<v[i-1].lv){
            v[i].st = -1;
            v[i].lv = v[i-1].lv;
        }
        else{
            v[i].st = max(v[i].ai, v[i-1].lv);
            v[i].lv = v[i].st + v[i].ti;
        }
        printf("%d%c", v[i].st, i==v.size()-1?'\n':' ');
    }
    return 0;
}

D 二叉排序树 [二叉搜索树]

题目描述

给定⼀个 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

思路

建立一颗二叉搜索树,记录父节点和左右孩子节点,数组中的下标就代表该节点,最后按序输出父节点即可。

代码

#include<bits/stdc++.h>
using namespace std;
struct node{
    int parent, lchild=0, rchild=0;
};
vector<node> v;

void locate(int root, int x){
    if(x>root){
        if(v[root].rchild) locate(v[root].rchild, x);
        else{
            v[root].rchild = x;
            v[x].parent = root;
            return ;
        }
    }
    else{
        if(v[root].lchild) locate(v[root].lchild, x);
        else{
            v[root].lchild = x;
            v[x].parent = root;
            return ;
        }
    }
}

int main(){
    int n, i, root, temp;
    scanf("%d", &n);
    v.resize(n+1);
    scanf("%d", &root);
    v[root].parent = 0;
    for(i=2; i<=n; i++){
        scanf("%d", &temp);
        locate(root, temp);
    }
    for(i=1; i<=n; i++) printf("%d%c", v[i].parent, i==n?'\n':' ');
    return 0;
}

E 序列 [动态规划]

题目描述

给定⼀个⻓为 n 的序列 A,其中序列中的元素都是 0~9 之间的整数,对于⼀个⻓度同样为 n 整数序列 B,定义其权值为 |A_i-B_i| (1<=i<=n) 之和加上 (B_j-B_j+1)^2 (1<=j<n) 之和。求所有⻓为 n 的整数序 列中,权值最⼩的序列的权值是多少。
【输⼊格式】
第⼀行⼀个整数 n (1<=n<=10^5),表示序列 A 的⻓度。 第⼆行 n 个整数 a1, a2, …, an (0<=ai<=9, 1<=i<=n),表示序列 A 中的元素。 【
输出格式】
仅⼀行⼀个整数,表示答案。
【样例输⼊】
6
1 4 2 8 5 7
【样例输出】
11

思路

复旦喜欢考dp,最后一题果然是……思路稍微卡了一下,但只要抓住dp的核心要点:确定「DP 状态」(最优子结构;无后效性)。一旦确定DP状态,那么状态转移方程自然出来了。最直接的想法是,将问题拆解为求前i个数的最小权值,但是发现这样明显不满足无后效性的要求。再从状态出发,思考什么是影响下一个步骤决策的因素———当前序列长度i和选择的0~9中哪个数j。那么就能写出状态转移方程:dp[i][j] = min(dp[i-1][k] + abs(j-now) + (j-k)^2)(for k=0; k<10; k++),其中i表示序列的第i个位置,j表示B_i,dp[i][j]表示序列在第i个位置选择填入B_i=j的最小值。

代码

#include<bits/stdc++.h>
using namespace std;
int dp[100010][10] = {0};

int main(){
    int n, i, j, k, temp, minn;
    scanf("%d", &n);
    scanf("%d", &temp);
    for(i=0; i<10; i++) dp[0][i] = abs(i-temp);
    for(i=1; i<n; i++){
        scanf("%d", &temp);
        for(j=0; j<10; j++){
            dp[i][j] = dp[i-1][0]+abs(j-temp)+j*j;
            for(k=1; k<10; k++) if(dp[i][j]>dp[i-1][k]+abs(j-temp)+(j-k)*(j-k)) dp[i][j]=dp[i-1][k]+abs(j-temp)+(j-k)*(j-k);
        }
    }
    minn = dp[n-1][0];
    for(i=1; i<10; i++) if(minn>dp[n-1][i]) minn=dp[n-1][i];
    printf("%d\n", minn);
    return 0;
}

2019年机试真题

A 计算日期 [字符串处理]

题目描述

输入一个 yyyymmdd 格式的时间,如 20190318,计算与 20190205 相差的天数,
取绝对值,所有输入在 19000101 和 21000101 之间。
样例输入:20190208

思路

分别计算给定日期和20190205到19000101的天数间隔,感觉会更加容易处理一些~

代码

#include<bits/stdc++.h>
using namespace std;

struct Time{
    int year, month, day;
};

int month[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};

int countdays(Time a){
    int y, i, d, ans=0;
    y=a.year-1900;
    d = a.day-1;
    ans = y*365 + (y-y%4)/4-1;
    for(i=0; i<a.month-1; i++) ans += month[i];
    ans+=d;
    return ans;
}

int main(){
    string s;
    Time t1, t0;
    t0.year = 2019;
    t0.month = 2;
    t0.day = 5;
    cin >> s;
    t1.year = stoi(s.substr(0,4));
    t1.month = stoi(s.substr(4,2));
    t1.day = stoi(s.substr(6,2));
    int day1 = countdays(t1), day2 = countdays(t0);
    printf("%d\n", (day1>day2)?(day1-day2):(day2-day1));
    return 0;
}

B 最大连续子序列和 [动态规划]

题目描述

给定一个数组及其大小,数组元素在[-106,106],元素个数最多 10^6 个。
输出最大的连续子序列和,题目保证最后结果为正数。

思路

经典动态规划问题,可拆解为计算前i个元素最大连续子序列和的子问题。设置一个数组f[i],存储终点为i的最大连续子序列和。

代码

#include<bits/stdc++.h>
using namespace std;
int f[1000010]={0};

int main(){
    int n, i, maxn;
    scanf("%d", &n);
    vector<int> v(n);
    for(i=0; i<n; i++) scanf("%d", &v[i]);
    f[0] = max(v[0], 0);
    maxn = f[0];
    for(i=1; i<n; i++){
        f[i] = max(f[i-1]+v[i],0);
        if(f[i]>maxn) maxn = f[i];
    }
    printf("%d\n", maxn);
    return 0;
}

C 二叉树形态总数 [动态规划]

题目描述

给定二叉树的节点总数 n,输出二叉树形态总数,n<= 1000
样例输入:3
输出:5

思路

本来以为是一道很简单的水题,结果发现事情并不像想像的那么简单……突然想起来DP的常见题型包含了方案总数类型,再想到要建一棵树首先是从根结点开始,再是左右子树~于是问题就被拆解为,左右子树的形态数有多少,最终将左右子树不同个数的排列组合数相加即可。

小技巧

动态规划常见形式:

  1. 求最大值/最小值
  2. 求可不可行
  3. 求方案总数
    动态规划的步骤:一是确定「DP 状态」(最优子结构;无后效性),二是确定「DP 转移方程」。

代码

#include<bits/stdc++.h>
using namespace std;
int f[1010]={1};

int main(){
    int n, i, j;
    scanf("%d", &n);
    f[1] = 1;
    for(i=2;i<=n;i++){
        for(j=0;j<=i-1;j++) f[i]+=f[j]*f[i-1-j];
    }
    printf("%d\n", f[n]);
    return 0;
}

2019年机试真题(工研院)

A 九键输入法 [字符串处理]

题目描述

实现九键输入,数字 2-9 对应九键中的 26 个字母,例如 2 代表 a,22 代表 b,222 代表 c,以此类推。0 表示空格,-表示停顿。若输入的字母按键相同,则中间应该用至少有一个“-”表示停顿。如输入 abc,则应是 2-22-222。现在保证输入仅仅含有数字 2-9,0,和停顿符“-”,并且可能存在无意义的停顿。要求输出最后的结果。
样例输入:
255
输出:
ak
样例输入:2-22—222
输出:
abc

思路

对只用26键的也太不友好了(我真不知道九键的字母排列啊)。。。常规的字符串题目,我使用了map进行映射,思路不复杂,细心做即可。

代码

#include<bits/stdc++.h>
using namespace std;

map<int, string> mp;

int main()
{
    for(int i=0; i<6; i++){
        mp[i+2] = ('a'+ i*3);
        mp[(i+2)*11] = ('b' + i*3);
        mp[(i+2)*111] = ('c' + i*3);
    }
    mp[7777] = 's';
    for(int i=0; i<2; i++){
        mp[i+8] = ('t'+ i*3);
        mp[(i+8)*11] = ('u' + i*3);
        mp[(i+8)*111] = ('v' + i*3);
    }
    mp[7777] = 'z';
    int i, j, last=0;
    string s, t;
    vector<int> v;
    cin >> s;
    while(s[0]=='-') s.erase(s.begin());
    for(i=0; i<s.size(); i++){
        if(s[i]=='-'){
            t = s.substr(last,i-last);
            int fi = 0;
            for(j=1; j<t.size(); j++){
                if(t[fi]!=t[j]){
                    v.push_back(stoi(t.substr(fi, j-fi)));
                    fi = j;
                }
            }
            v.push_back(stoi(t.substr(fi, t.size()-fi)));
            while(s[i]=='-') i++;
            last = i;
        }
    }
    if(last<s.size()){
        t = s.substr(last,s.size()-last);
        int fi = 0;
        for(j=1; j<t.size(); j++){
            if(t[fi]!=t[j]){
                v.push_back(stoi(t.substr(fi, j-fi)));
                fi = j;
            }
        }
        v.push_back(stoi(t.substr(fi, t.size()-fi)));
    }
    for(i=0;i<v.size();i++){
        cout << mp[v[i]];
    }
    cout << endl;
    return 0;
}

B 计算负载 [水题]

题目描述

给定一个大小为 n 的数组,代表昨天的负载情况。给定数字 t,代表今天变化的次数,下面 t 行,每行三个整数 a,b,c。表示序号 a 到 b 之间所有的负载变化了 c(c 可能是负数)。给定数字 k,表示查询的次数,接下来 k 行,每行两个整数 i,j,表示要查询从 i 到 j 之间的负载总和。要求输出这 k 次查询的结果。
样例输入:
5
1 2 2 -1 3
2
2 4 3
1 3 -1
1
2 4
输出:
10

思路

一开始走了弯路,想要一步到位直接计算从第0个位置到第i个位置的总和,最后发现每次更新负载,都要将从a开所有的v[i]都重新计算一遍(v[i]表示前i个负载的总和),所以最后老老实实在更新完负载后再重新计算v[i]。这样查询时就不用一次次遍历。

代码

#include<bits/stdc++.h>
using namespace std;

int main()
{
    int n, t, k, i, j, a, b, c;
    scanf("%d", &n);
    vector<int> v(n+1);
    v[0] = 0;
    for(i=1; i<=n; i++) scanf("%d", &v[i]);
    scanf("%d", &t);
    for(i=0; i<t; i++){
        scanf("%d %d %d", &a, &b, &c);
        for(j=a; j<=b; j++) v[j] += c;
    }
    for(i=1; i<=n; i++) v[i] = v[i-1]+v[i];
    scanf("%d", &k);
    for(i=0; i<k; i++){
        scanf("%d %d", &a, &b);
        printf("%d%c", v[b]-v[a-1], i==k-1?'\n':' ');
    }
    return 0;
}

C 通信代价 [动态规划]

题目描述

给定一个无向图,保证是一棵树,定义两个结点 ab 之间的通信代价为 ab 路径上的边的数目,节点 a 的通信代价为 a 到其他所有节点的通信代价之和。在一行中输出所有结点的通信代价。输入的一行数字表示节点数,下面每行各有两个数字a和b,表示节点a到节点b有直接路径。
样例输入:
4
1 2
2 3
2 4
输出:
5 3 5 5

思路

这道题不是很会做,代码只能保证过样例,O(n2)的复杂度。最直接的想法是Floyd,但是O(n3)的复杂度基本会超时的。。洛谷有道差不多的题目:1395。跑下来有MLE也有WA。。但真没找到代码问题在哪,就先不研究了。我的大致思路是,每一条新的路径,必定有一个端点是新加入的点(否则就会形成环,第一条路径两个端点都是新端点),假设新端点为b,旧端点为a,那么其他已经加入集合的点到b的距离就等于该点到a点距离再加一,就这样一直更新整个路径数组。

代码

#include<bits/stdc++.h>
using namespace std;
struct node{
    vector<int> dis;
};

bool exist[100000]; //判断节点是否已经存入

int main()
{
    int n, i, j, a, b, t, now, ans;
    scanf("%d", &n);
    vector<node> v(n+1); //存储二维矩阵
    vector<int> in; //存储已经加入的节点
    for(i=0; i<=n; i++) v[i].dis.resize(n+1);
    scanf("%d %d", &a, &b);
    v[a].dis[b] = v[b].dis[a] = 1;
    exist[a] = exist[b] = true;
    in.push_back(a);
    in.push_back(b);
    for(i=1; i<n-1; i++){
        scanf("%d %d", &a, &b);
        v[a].dis[b] = v[b].dis[a] = 1;
        if(exist[a]){
            t = b;
            now = a;
        }
        else{
            t = a;
            now = b;
        }
        exist[t] = 1;
        for(j=0; j<in.size(); j++){
            if(in[j]!=now) v[in[j]].dis[t] = v[t].dis[in[j]] = v[in[j]].dis[now] + 1;
        }
        in.push_back(t);
    }
    for(i=1; i<=n; i++){
        ans = 0;
        for(j=1; j<=n; j++) ans+=v[i].dis[j];
        printf("%d%c", ans, j==n?'\n':' ');
    }
    return 0;
}

2018年机试真题

A 求众数 [排序]

题目描述

众数就是一个序列中出现次数最多的数字。 如果不唯一,则输出小的那个值。给定第一个代表有几个数字。 1<=n<=10^5 每个数字在 int 范围内。
样例:
输入 (第一个代表有几个数字)
8
10 3 8 8 3 2 2 2
输出
2

思路

对输入的数字进行排序,统计最多重复个数的数字即可。(惊喜地发现还有学长的题解,顺便贴一下~学长想到开数组和map,其实我一开始也是这么做的。。后来发现可以不用map)

学长的题解

第一题本来用 int num[] 开一个数组放里面记录次数的。但是后来发现每个数字在 int 范围,开不了那么大,就 map 做了。

代码

#include<bits/stdc++.h>
using namespace std;

int main()
{
    int n, i, res, cnt=0, now=0, maxn=0;
    scanf("%d", &n);
    vector<int> v(n);
    for(i=0; i<n; i++) scanf("%d", &v[i]);
    sort(v.begin(),v.end());
    for(i=0; i<n; i++){
        if(v[i]!=now){
            if(cnt>maxn){
                maxn = cnt;
                res = v[i-1];
            }
            cnt = 1;
            now = v[i];
        }
        else cnt++;
    }
    if(maxn==0) res = v[0];
    if(cnt>maxn) res = v[n-1];
    printf("%d\n", res);
    return 0;
}

B 解方程 [字符串处理]

题目描述

给定一个字符串,代表一个 一元一次方程。 如果有解求解,输出格式“x=数字” ,如果解的个数无穷,输出 “infinite solutions”。 如果 没有解输出“no solution”,字符串长度不超过 256 。
样例:
10x-2x-8=4x+7+x
输出:
x=5

思路

字符串的题目大多不难,就是小细节很多,先对输入的字符串进行处理,如果只含x项,则在前面加上系数"1"统一处理,如果末尾不是"x",我随便加了个"n",也是为了方便处理。因为不知道系数和结果的具体类型,担心有数字为double类型所以使用的是stod()函数,不过看输出结果应该是保证整数的。。。这道题做了好久。。。麻了~

学长的题解

做的时间最长的题目,各位模拟吧。 记录左边的纯数字和,右边的纯数字和。还有 x 的两侧的系数和。如果 x 的系数不为0,则有解,如果为0,再判断左数字和 , 右数字和是否相同,判断是否解唯一。
注: 这个题我觉得有坑点。 首先就是 256 个字符,估计是大整数的,但是我来不及做,2个小时3道题。 另外就是题目没有说“给定序列一定满足是有意义的”。 也就是说 是否可能 没有 x ,没有等号呢,没有数字呢。 有的就不会成为有效地。 但是 3=8 这个要输出 no solution 的

代码

#include<bits/stdc++.h>
using namespace std;

int main()
{
    int i, st=0;
    bool left = true;
    double temp, ans, xnum=0, num=0;
    string s;
    cin >> s;
    for(i=0; i<s.size(); i++) if(s[i]=='x' && (i==0||s[i-1]<'0'||s[i-1]>'9')) s.insert(i, "1");
    if(s[s.size()-1]!='x') s.insert(s.size(),"n");
    i=0;
    if(s[0]=='-') i++;
    for(; i<s.size(); i++){
        while(((s[i]<='9'&&s[i]>='0')||s[i]=='.')&&i<s.size()) i++;
        temp = stod(s.substr(st,i-st));
        if(!left) temp = -temp;
        if(s[i]=='x'){
            xnum += temp;
            if(i<s.size()&&s[i+1]=='='){
                i++;
                left = false;
            }
            i++;
        }
        else if(s[i]=='='){
            left = false;
            num += temp;
            i++;
        }
        else num += temp;
        st = i;
    }
    if(xnum==0&&num==0) printf("infinite solutions\n");
    else if(xnum==0&&num!=0) printf("no solution\n");
    else{
        ans = num/xnum;
        printf("x=%.2f\n", -ans);
    }
    return 0;
}

C 骨牌 [动态规划]

题目描述

有2n 的地板,用12和 2*1 的骨牌进行铺地板。问共有多少种情况。结果对 999983 取余 。1<=n<=10000。
样例:
6
输出:
13

思路

关键还是在确立DP状态,要找到无后效性的最优子结构。最终状态转移方程为:v[i] = v[i-2]*2 + v[i-3].(与学长的递推本质一致,是我想复杂了)

学长的题解

简单 DP 问题。 从第三个开始,dp = (dp[i-1]+dp[i-2]) % 999983。

代码

#include<bits/stdc++.h>
using namespace std;

int main()
{
    int i, n;
    scanf("%d", &n);
    vector<int> v(n+1);
    v[0] = 1;
    v[1]=1;
    v[2]=2;
    for(i=3; i<=n; i++) v[i] = (v[i-2]*2 + v[i-3])%999983;
    printf("%d\n",v[n]);
    return 0;
}

2017年机试真题

A 求中位数 [排序]

题目描述

给定一个整数序列,求中位数。

思路

输入输出都没有……这道题随便做做吧~

代码

#include<bits/stdc++.h>
using namespace std;

int main()
{
    int n, i;
    scanf("%d", &n);
    vector<int> v(n);
    for(i=0; i<n; i++) scanf("%d", &v[i]);
    sort(v.begin(), v.end());
    printf("%d\n", v[n/2]);
    return 0;
}

B 求解校验码 [字符串处理]

题目描述

给定一个9位数字的ISBN,求其校验位。ISBN格式为2-02-033598,校验位的计算方法如下:从左到右依次将各位数字乘10,9,8,……,2,求出其和S,作模运算得M=S mod 11。若11-M在1和9之间,校验位即为该数字;若11-M等于10,校验位为X;11-M等于11,校验位为0。输出添加校验位的ISBN,如2-02-033598-0。

思路

挺简单的字符串处理。

代码

#include<bits/stdc++.h>
using namespace std;

int main()
{
    int i, j=10, sum=0;
    string s;
    cin >> s;
    for(i=0; i<s.size(); i++){
        if(s[i]=='-') continue;
        else{
            sum += stoi(s.substr(i,1))*j;
            j--;
        }
    }
    sum = 11-(sum%11);
    cout << s << "-";
    if(sum<=9) cout << s << endl;
    else if(sum==10) cout << "X" << endl;
    else cout << "0" << endl;
    return 0;
}

C 无向图 [图]

题目描述

一个无向图,顶点为N个,顶点编号为1~N,其中M条边已给定,现在要从K条备选边中选出若干条,使得整个图连通,且选出的边权值和最小。
输入
第一行输入三个整数N(N<100), M, K,接下来一行为K个整数表示备选边的编号。然后是是M行,每行三个数字:u,v,d(0<d<10000)表示结点u和结点v的边,权值为d,编号按照输入输入顺序依次为1~M。
输出
如果输入有解则输出选出的边的权值和否则输出-1

思路

由于是给定边的的信息,所以考虑 Kruskal 算法,利用并查集实现。(好久没做过图的题了,基本抄了一遍学习一下)

代码

#include<bits/stdc++.h>
using namespace std;

struct Edge{
    int u, v, d;
}se[6000];

int f[110], ke[6000];

bool cmp(Edge a, Edge b){
    return a.d<b.d;
}

int getf(int x){
    return x==f[x]?x:(f[x]=getf(f[x]));
}

int main()
{
    int n, m, k, i, u, v, d, j=0, q=0, sum=0;
    scanf("%d %d %d", &n, &m, &k);
    for(i=0; i<k; i++) scanf("%d", &ke[i]);
    sort(ke, ke+k);
    for(i=1; i<=m; i++){
        scanf("%d %d %d", &u, &v, &d);
        if(i==ke[j]){
            se[j].u = u;
            se[j].v = v;
            se[j].d = d;
            j++;
        }
    }
    for(i=1; i<=n; i++) f[i] = i;
    sort(se, se+k, cmp);
    for(i=0; i<k; i++){
        int ru = getf(se[i].u);
        int rv = getf(se[i].v);
        if(ru!=rv){
            f[ru]=rv;
            sum+=se[i].d;
            q++;
        }
        if(q==n-1) break;
    }
    if(q==n-1) printf("%d", sum);
    else printf("-1");
    return 0;
}
  • 15
    点赞
  • 79
    收藏
    觉得还不错? 一键收藏
  • 10
    评论
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值