目录

第一题 Stepping Numbers

题意

思路

代码

反思

第二题 Nodes from the Root

题意

思路

代码

大佬的标准题解代码:

菜鸡我的又费空间,又费时间,又臭又长,思路又蠢的垃圾代码:

反思

第三题 Distinct Subsequences

题意

思路

代码

大佬的标准题解代码:

菜鸡我的又费空间,又费时间,又臭又长,思路又蠢的垃圾代码:

反思

结语

参考资料

今天参加了群里的一个模拟训练,训练的题目是19年的南大的题,写了三个小时,啥都没写出来,一直在写第一题,倒是有些思路,但是还是没写出来,模拟训练结束后又磨了2个小时,唉,还是放弃这道题看题解了,南大路漫漫啊~~~

第一题 Stepping Numbers

题意

给定l , r ( 0 ≤ l ≤ r ≤ 3 e 8 ) l,r(0≤l≤r≤3e8)l,r(0≤l≤r≤3e8),问[ l , r ] [l,r][l,r]中的自然数满足下面条件的数有多少个。

条件:数字的任意相邻两位差值都恰好为1,且数字至少有两位。

思路

本题l , r l,rl,r看上去挺大,但容易观察到, 3 e 8 3e83e8以内满足条件的数其实很少,那怎样知道大致有多少呢?能不能暴力搜索呢?(请不要尝试将区间内所有数字都检查一遍,复杂度太高了)

若将相邻后一位减去前一位的大小记在一个数组中,则该数组应只含 1 或 -1,也就是除了第一位以外都有两种选择(没错,很像二进制!),而第一位只受区间大小限制。

这样,我们就能大致估测满足条件的数不超过10 × 2 8 ( < 1 0 4 ) 10×2^8(<10^4)10×28(<104),因此不会超时。很明显,能够直接暴力构造 (注意是暴力构造,而不是纯暴力)

当然暴力也有好几种,不管是一顿for循环还是不断dfs,都是高效的。

扩展:简单的采用记忆化的化就能处理r 小 于 等 于 1 0 1 e 5 r 小于等于 10^{1e5}r小于等于101e5数量级的问题了(简称数位DP)

代码

#include "bits/stdc++.h"

using namespace std;

int p[10];

int n,m;

int dfs(int pos,int pre,int cur){

//pos代表当前是数字中的哪一位,pre代表这一位前一位的数字,cur代表当前这一位的前缀的大小,比如1234,2的cur的大小就是1000,3就是1200.

//res存储的是在以cur前缀下,有多少Stepping Number

int result = 0;

/*递归边界:

如果递归到达了个位数后面的数字,那么进行判断,如果这个数字cur在要求范围内,则加1,否则返回0

*/

if(pos == -1){

//这个条件判断式设计的很好

if(cur >= max(n,10) && cur <= m){

result = 1;

return result;

}else{

result = 0;

return result;

}

}

//当前前缀超了m?那就进行剪枝

if(cur > m){

result = 0;

return result;

}

//如果当前的前缀的大小是0~,那么这个位置就是自定义的

if(cur == 0){

for(int i=0;i<=9;i++){

result += dfs(pos-1,i,i*p[pos]);

}

}else{

//如果当前的前缀的大小不是0~,那么说明这里不能乱放数字,要根据前一位进行判断

//后面减前面为-1的情况

if(pre > 0){

result += dfs(pos-1,pre-1,cur+(pre-1)*p[pos]);

}

//后面减前面为1的情况

if(pre < 9){

result += dfs(pos-1,pre+1,cur+(pre+1)*p[pos]);

}

}

return result;

}

int main(){

p[0] = 1;

for(int i=1;i<=10;i++){

p[i] = p[i-1] * 10;

}

int t;

scanf("%d",&t);

while(t--){

scanf("%d %d",&n,&m);

//我们这里的数据的范围是3e8,所以我们设置最高位是10e8,曲线救国,再加一个前缀0,即像这样 "0 _ _ _ _ _ _ _ _ _"

printf("%d\n",dfs(8,0,0));

}

}

反思

由于许多同学不太了解评测环境1s只能运算1e8~1e9次(大部分评测环境),导致第一题的大量超时提交,交题之前应先计算复杂度,这是以后需要注意的。

除了时间限制,还需要注意内存限制,32M空间限制不要开超过1000W的int变量(C++),其它空间限制可自行计算。

我自己看到这道题的第一感觉是使用动态规划,因为读了题目之后感觉有不断细化成子问题的味道在里面,但是最后弄了半天也没有弄出来。。。

看了大佬的题解,他通过分析,发现可以直接从最高位(即1 0 8 10^8108)开始,向着低位出发,直到最后一位来构建出所有的stepping numbers,而且他也欲判过不会超时。

关于递归,DFS,动态规划的关系:

DFS可以用递归实现,也可以不用递归实现;

动态规划可以用DFS实现,此时的动态规划称为记忆化搜索,刚好这个搜索对应了DFS中的"S".动态规划一般用递推for循环实现,至少是这样实现的.

这个题目貌似真的可以用动态规划去实现,来减少DFS过程中的重复操作,后面再看吧.

大佬的代码实在是太简洁了,搞得我看了半天都没看懂…菜是原罪…

学会了使用include "bits/stdc++.h"

注意dfs()函数的涵义:返回某前缀下所有的stepping number数,因此算是划分了子问题.

第二题 Nodes from the Root

题意

给定一棵带边权树(原题是二叉的,加强一下数据啦,考察邻接矩阵知识),n ( n ≤ 2 e 4 ) n(n≤2e4)n(n≤2e4)个节点,边权不大于1 e 7 1e71e7,然后给定一个Y ( 1 ≤ Y ≤ n ) Y(1≤Y≤n)Y(1≤Y≤n),求最小的X ( X ≥ 0 ) X(X≥0)X(X≥0).

X XX表示边权小于X XX的边都会被关闭,Y YY表示关闭这些边以后从根节点能到达的点的数量不超过Y YY.

思路

容易想到,对某一个结点,若从根结点到它的简单路径上至少有一条边被关闭,那么它就是无法到达的点。那么,如何得到这条简单路径的信息呢?

从根结点做一遍dfs就可以了!更进一步,我们想要的是最小化X XX,因此我们先找到要关闭某个结点所需要的最小代价,这在dfs的过程中就能完成(取路径上最小的边权)。(时间复杂度为O ( n ) O(n)O(n))

最后,只需要把这些代价存放到一个数组mi里面,从小到大排个序(当然用sort啦),然后直接输出mi[n-Y]+1就好了,复杂度为 O ( n l o g 2 n ) O(nlog_2n)O(nlog2​n)(因为sort的时间复杂度是这个)。

代码

大佬的标准题解代码:

#include "bits/stdc++.h"

using namespace std;

const int maxn = 2e4+7;

int n, Y;

int head[maxn], to[maxn*2], w[maxn*2], nxt[maxn*2], tot; //更常见的是用vector存边

int mi[maxn];

inline void add_edge(int u, int v, int c) {

++tot; to[tot]=v; w[tot]=c; nxt[tot]=head[u]; head[u]=tot;

++tot; to[tot]=u; w[tot]=c; nxt[tot]=head[v]; head[v]=tot;

}

void dfs(int u, int f, int m) {

mi[u]=m;

for(int i=head[u]; i; i=nxt[i]) { //采用自己喜欢的遍历方式即可

int v=to[i]; if(v==f) continue;

dfs(v,u,min(m,w[i]));

}

}

int main() {

int T; scanf("%d", &T);

while(T--) {

scanf("%d%d", &n, &Y);

for(int i=1; i

int u, v, c;

scanf("%d%d%d", &u, &v, &c);

add_edge(u+1,v+1,c); //采用自己喜欢的连边方式即可

}

dfs(1,0,1<<30);

sort(mi+1,mi+1+n);

if(n-Y==0) printf("0\n"); //这里特判一下

else printf("%d\n", mi[n-Y]+1); //+1是因为题目要求严格小于

for(int i=1; i<=n; ++i) head[i]=0; //多组数据别忘了初始化

tot=0;

}

}

菜鸡我的又费空间,又费时间,又臭又长,思路又蠢的垃圾代码:

#include "bits/stdc++.h"

#include

#include

#include

using namespace std;

const int maxn = 2e4+10;

struct node{

int v;

int w;

};

//用于存储输入的图的数量

int t;

//用于存储边和数量

int n,y;

//用于存储输入的边和权重

int u,v,w;

//邻接表存储一个结点连接的边和权重,需要每次初始化

vector adj[maxn];

//储存每个结点的孩子的数量,不需要每次初始化

int childNum[maxn];

//存储权重的集合

set st;

//存储权重的数组

int weights[maxn];

//用于表示下标是否被访问

bool vis[maxn];

int countChildNum(int root,int f){

//f表示父亲结点的编号

//初始化为1

int number = 1;

//这里面自己蕴含了递归边界!!!

for(int i=0;i

if(adj[root][i].v == f){

//如果这个结点是自己的父节点,则直接下次循环

continue;

}else{

number += countChildNum(adj[root][i].v,root);

}

}

childNum[root] = number;

return number;

}

int countNodes(int x){

//输入x下,根结点所拥有的结点

//采用层序遍历进行计算

int number = childNum[0];

//每次进来都要初始化,之前因为这个一直没过题

fill(vis,vis+maxn,false);

queue q;

q.push(0);

vis[0] = true;

while(!q.empty()){

int cur = q.front();

q.pop();

//设置已经访问

vis[cur] = true;

for(int i=0;i

if( vis[adj[cur][i].v] == true ){

continue;

}else if( (adj[cur][i].w) < x ){

//小于x,则进行删除

number -= childNum[adj[cur][i].v];

}else{

//入队

q.push(adj[cur][i].v);

}

}

}

return number;

}

int search(int l,int r){

//通过二分进行查找

int number = 0;

while(l != r){

int mid = (l + r) / 2;

number = countNodes(weights[mid]);

if(number > y){

l = mid + 1;

}else if(number <= y){

r = mid;

}

}

if(l==0){

return 0;

}else{

return weights[l-1]+1;

}

}

int main(){

scanf("%d",&t);

while(t--){

scanf("%d %d",&n,&y);

//注意一些共用的变量每次使用要进行初始化

for(int i=0;i

adj[i].clear();

}

st.clear();

//读入数据

for(int i=0;i

scanf("%d %d %d",&u,&v,&w);

node n1;

node n2;

n1.v = v;

n1.w = w;

n2.v = u;

n2.w = w;

adj[u].push_back(n1);

adj[v].push_back(n2);

st.insert(w);

}

//dfs计算每个结点的孩子结点数量

countChildNum(0,-1);

int k=0;

for(set::iterator it = st.begin();it != st.end();it++){

weights[k++] = *(it);

}

//二分查找最佳的x

int ans = search(0,k-1);

printf("%d\n",ans);

}

}

反思

像这种多组测试数据一次性输入时,不要忘记每次循环都要对共享的数据进行初始化;

大佬代码反思:

我与大佬的区别应该就是大佬会想到使用一次dfs()来找到每个结点不可达的临界条件,然后后面直接使用一个数组把这些临界条件存储起来。再排个序,这样直接可以得到结果,非常快,时间复杂度为O ( n l o g 2 n ) O(nlog_2n)O(nlog2​n);

使用了链式前向星来加边和存储二叉树(这个高级了,像普通机试用用vector就好了);

菜鸟代码反思:

一开始以为u v w中u就是起点,v就是终点,但是并不是这样的,题目并没有这么说,说明自己读题不仔细,想当然了;

第一次接触通过边来建立二叉树的题目,一时间不知道怎么处理。一开始我想一开始就变成有向图,但是基于1的原因,是不可能的,所以最终只能变成无向图。使用邻接矩阵,因为使用二维数组会爆炸的;

对于2的无向图,怎么变成树呢?

其实通过在dfs时,从root结点开始遍历目标结点就自然而然形成树了,只需要加入一些限制条件,防止结点访问它的父结点即可,你可以使用一个记录是否已访问的bool数组(需要注意的是,每次dfs之前都要进行初始化!!!),也可以把父亲结点的下标传给子节点,我写的时候太混乱了,两种都用了。

对变量的范围的理解。每一个变量都有自己的取值,写题目的时候要考虑范围的边界值,比如这里Y YY等于0和N时,就要好好考虑一下!!!之前忽略了,做题的习惯还是不行;

时间复杂度分析,时间复杂度应该是 3 ∗ n l o g 2 n 3*nlog_2n3∗nlog2​n(因为有左右孩子还有父亲结点,所以有个3)吧,不知道分析错没有。。。

综上,发现我自己虽然会一些算法思想,但是在发现题目中的规律的能力不足,发掘不了深层次的规律,读题也不认真,容易想当然。

第三题 Distinct Subsequences

题意

给定两个串S , T S,TS,T ( ∣ S ∣ , ∣ T ∣ ≤ 1 e 4 |S|,|T|≤1e4∣S∣,∣T∣≤1e4 ) T TT串每一个字符都是随机得到的),问S SS串中有多少个子序列等于T TT。

要求答案对1 e 9 + 7 1e9+71e9+7取模,原题其实是保证了答案不爆i n t intint的,但由于造数据的时候很难保证答案在不爆i n t intint的情况下还足够的强(你们懂的,数据不强容易被各种暴力做法莽过去),因此造数据的时候就造得尽可能大,但是不太清楚大家是否都了解取模的规则(离散数学里面应该学了一点的QAQ)。(我还真不知道怎么取模,我记得我离散学过呀,我记得我那时就不会。。。还有我不懂module是取模的意思。。。)

这里用到的取模知识是:

(a + b) % p = (a % p + b % p) % p (1)

(a - b) % p = (a % p - b % p) % p (2)

(a * b) % p = (a % p * b % p) % p (3)

ab % p = ((a % p)b) % p (4)

思路

这题不能直接使用n 2 n^2n2进行暴力dp求解,因为评测环境1s只能运算1e8~1e9次(大部分评测环境),使用n 2 n^2n2的话会超时。

动态规划常见有两种用途,一种是最优化方案,另一种就是统计方案数。如果分别用一句话来描述这两种用途的特点,我会这样描述:

最优化方案:将多个子问题的方案取最优的那一个作为代表,去更新后续答案。

统计方案数:将所有对后续子问题相同影响的方案放在一起,去更新后续答案。

代码

大佬的标准题解代码:

#include "bits/stdc++.h"

using namespace std;

const int maxn = 1e4+7;

const int mod = 1e9+7;

char s[maxn], t[maxn];

int pos[26][maxn], cnt[26]; //pos[i][j]记录字母'a'+i在T串上的所有位置(递增排列),cnt则记录数量

long long dp[maxn];

int main() {

int T; scanf("%d", &T);

while(T--) {

scanf("%s%s", s+1, t+1);

int n=strlen(s+1);

int m=strlen(t+1);

memset(cnt,0,sizeof(cnt));

for(int i=1; i<=m; ++i) dp[i]=0;

dp[0]=1;

for(int i=1; t[i]; ++i) {

int c=t[i]-'a';

cnt[c]++; //记录数量

pos[c][cnt[c]]=i; //记录位置

}

for(int i=1; s[i]; ++i) { //一位一位的枚举

int c=s[i]-'a';

for(int j=cnt[c]; j; --j) { //从后往前枚举这个字母在T串的所有位置

dp[pos[c][j]]=(dp[pos[c][j]]+dp[pos[c][j]-1])%mod;

}

}

printf("%lld\n", dp[m]);

}

}

菜鸡我的又费空间,又费时间,又臭又长,思路又蠢的垃圾代码:

#include "bits/stdc++.h"

#include

using namespace std;

const int maxn = 1e4 + 10;

const long long mod = 1e9+7;

const int maxm = 130;

typedef long long ll;

//s数组,这个string数组就很灵性,没说是字母数组哦

char s[maxn];

//s数组的长度

int slen;

//t数组

char t[maxn];

//t数组的长度

int tlen;

//输入测试用例数

int Q;

//dp数组

//dp[i][j]表示在s的第i位,满足后缀第j位的数量

//由于是dp,所以,只要计算当前的就好了吧

//注意数值可能很大,使用long long

ll dp[maxn];

//用于保存映射,记录每个字符在哪个位置

vector mp[maxm];

int main(){

scanf("%d",&Q);

while(Q--){

//初始化dp数组

fill(dp,dp+maxn,0);

//初始化映射数组

for(int i=0;i

mp[i].clear();

}

//这一位设为1

dp[0] = 1;

//记录当前哪里为之前的

int idx = 0;

int curidx;

s[0] = 'a';

t[0] = 'a';

scanf("%s",s+1);

scanf("%s",t+1);

//居然有点忘记strlen怎么写了

slen = strlen(s) - 1;

tlen = strlen(t) - 1;

//将t的各个字符的位置读取到mp中

for(int i=1;i<=tlen;++i){

int num = t[i];

mp[num].push_back(i);

}

int cur;

for(int i=1;i<=slen;++i){

//获取当前字符

cur = s[i];

for(int j=mp[cur].size();j>0;--j){

int curpos = mp[cur][j-1];

dp[curpos] = (dp[curpos-1] + dp[curpos]) % mod ;

}

}

printf("%lld\n",dp[tlen]);

}

return 0;

}

反思

连单词 “module” 是 “取模”的意思我都不知道;

注意要使用long long,我终于学会分析数据的大小啦;

dp数组降为一维很重要~~~;

这道题帮我重新回顾了取模的一些知识,希望以后有用;

这道dp题对我来说不太难,至少最后我的思路和代码和大佬基本差不多啦,有点小激动;

刚那道题的时候,心想自己在算法笔记上好像见过这个知识点,然后自己记不起来了。最后写完发现,我以为的那个其实是KMP算法,跟这个没有啥关系;

最后有个小疑问,为啥大佬的代码里面确定这道题的字符串就是小写字母呢。。。

结语

终于磨了5天把三道题写完了,撒花,这周还有,继续冲,加油(每天还要上课滴,学习不能丢~~~),我感觉这次南大的题不像我在PAT做的大多题那样直接套模板就好了,它是有一些考验智商的,比如第一题和第二题。。。我是真的没想到,只会套板子。。。

最后,树,DFS和动态规划是这次机试的考察重点!

参考资料

【赛题网站】2019南京大学计算机考研复试机试题

【题解来源】2019南京大学计算机考研复试机试题分享

南京大学java机试,2019年南京大学计算机考研复试机试真题相关推荐

  1. 计算机考研复试【面试真题】

    前言 本人为21考研,所报专业为计算机科学与技术,下面记录一下本人参加复试时被问的问题~(专业课科目为数据库原理) 1. 数据库系统的组成 2. 数据库恢复技术 3. 数据库故障有哪些 4. 最喜欢的 ...

  2. 华师大计算机在线作业,华东师范大学计算机考研复试机试习题

    华东师范大学计算机考研复试机试习题 华东师范大学计算机考研:计算机系.数据学院复试机试历年真题以及AC代码.历年学长总结得到.适用学院:计算机学院.数据学院.软件学院也可参考.sum/=10;prin ...

  3. 华科计算机考研复试真题,华科计算机考研复试机试题(2000-2013)

    华科计算机考研复试机试题(2000-2013),c++实现,注本人参加过2014年华科上机考试,老师说机试时可以使用c语言,c++语言. 2000年 阶乘 #include #include #inc ...

  4. 2011年华科计算机考研复试机试题真题

    很好的资料哦,更多资料请访问王道论坛:www.cskaoyan.com 2011年华科计算机考研复试机试题真题:

  5. 厦门大学计算机学院离散数学复试,2019年厦门大学计算机考研复试办法

    2019年厦门大学计算机考研复试办法如下~ 1.学术型硕士研究生总计划招生25名,其中推荐免试生13名已经复试.今年上复试线考生26名,按照1:1.5的差额复试比例确定初试成绩在337分及以上的考生全 ...

  6. 华科计算机考研复试真题,华科计算机考研复试机试题【含参考代码】

    华科计算机考研复试机试题[含参考代码] (32页) 本资源提供全文预览,点击全文预览即可全文预览,如果喜欢文档就下载吧,查找使用更方便哦! 29.9 积分 华科历年复试机试题汇总上机考试.一般网站上公 ...

  7. 清华计算机考研复试机试,清华大学历年考研复试机试真题 - 论文

    题目描述 Time Limit: 1000 ms Memory Limit: 256 mb 小H为了完成一篇论文,一共要完成n个实验.其中第i个实验需要ai的时间去完成. 小H可以同时进行若干实验,但 ...

  8. 上海交大计算机考研复试,上海交大计算机考研复试机试

    上海交大 计算机考研 SJTU-CS 复试机试 (2005-2010) 题目 我自己在准备考研时曾做了下06,07,08,09年的题目,并且在博客中提供了一个参考的题解,10年的题目以及11年保研的题 ...

  9. 华科00年计算机考研复试机试

    [1]输入n, 求y1=1!+3!+...m!(m是小于等于n的最大奇数) y2=2!+4!+...p!(p是小于等于n的最大偶数) 参考代码: #include<stdio.h> int ...

最新文章

  1. 【云栖直播】精彩推荐第2期:首届阿里巴巴研发效能嘉年华
  2. iOS进阶之架构设计MVVM模式仿新闻项目(6)
  3. 【Python基础】字符串专题总结
  4. css 控制溢出文本显示省略号效果
  5. 交流电的有效值rms值_【电工基础知识:三、正弦交流电的产生】2正弦交流电的三要素...
  6. Visual Studio 2008 C++添加 链接库
  7. 海外召回1700辆,奔驰首款电动汽车要在中国上市了
  8. 《社交网站界面设计(原书第2版)》——3.9 使用生命周期
  9. jQuery插件开发之windowScroll
  10. Gerrit配置--用户配置
  11. 推荐系统与GNN擦出的火花竟如此绚丽多彩
  12. 专业RAW图像处理软件Capture One Pro 22
  13. Linux测网速工具,Linux中上下行网速测试工具 speedtest-cli
  14. java网吧会员计费管理系统springboot+vue
  15. android微信配色,万能微信公众号配色模板(神仙配色太好看了)
  16. 大厂的安卓技术面试是酱紫的
  17. 设计了一款 IGBT单脉冲、双脉冲测试波形信号发生器
  18. 哥伦比亚大学 NLP 第三章(第二部分)
  19. 不同部位长青春痘说明不同器官有毛病吗? (转自 八月的阳光)
  20. #1045 无法登录 MySQL 服务器(实际上是我第一次使用,不知道密码)

热门文章

  1. 想做游戏开发?Unity3D值得你了解一下!
  2. 预防猪流感(甲型H1N1流感)香囊...有备无患
  3. 虚拟机装win7,磁盘格式化后重启,提示找不到系统
  4. Linux安装本地打印机教程
  5. 诺基亚开放Symbian系统源码
  6. 名片管理系统--python入门项目
  7. 前端页面引入外部字体 @font-face 的使用方法
  8. 鸿蒙智慧屏安装apk,华为智慧屏如何安装第三方APP,看看这篇你就知道了!
  9. 2021-10-14 ContextType(MIME) 与 Java文件上传/下载
  10. 一文读懂高温厚膜电路