华为 暑期实习 2023.04.26 机试题目与题解

1、批量初始化次数

某部门在开发一个代码分析工具,需要分析代码模块之间的依赖关系,用来确定模块的初始化顺序、是否有循环依赖等问题。"批量初始化"是指一次可以初始化一个或多个模块,例如模块1依赖模块2、模块3也依赖模块2,但模块1和3没有依赖关系。则必须先"批量初始化"模块2,再"批量初始化"模块1和3。现给定一组模块间的依赖关系请计算需要"批星初始化"的次数。

输入

  1. 第1行只有一个数字,表示模块总数N。
  2. 随后的N行依次表示模块1到N的依赖数据。每行的第1个数据表示依赖的模块数量(不会超过N),之后的数字表示当前模块依赖的模块ID序列,该序列不会重复出现相同的数字模块ID的取值一定在[1,N]之内。
  3. 模块总数N取值范围 1<=N<=1000。
  4. 每一行里面的数字按1个空格分隔。

输出

输出"批量初始化次数",若有循环依赖无法完成初始化,则输出-1。

样例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
输入:
5
3 2 3 4
1 5
1 5
1 5
0

输出:
3

提示:共5个模块。模块1依赖模块2、3、4;模块2依赖模块5;模块3依赖模块5;模块4依赖模块5;模块5没有依赖任何模块。
批量初始化顺序为{5}->{2,3,4}->{1},共需"批量初始化"3次。

输入:
3
1 2
1 3
1 1

输出:
-1

提示:存在循环依赖,无法完成初始化,返回-1。

解答

不难想到使用拓扑排序解决。一个模块依赖于另一个模块意味着这两个模块不能同时初始化。如果找出一条有向路径,那么这条路径上前面的节点必须先于后面节点的初始化。因此,这道题本质上是求:

有向无环图DAG上的最长路。

这是一个经典的动态规划问题,此处不赘述。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# include <bits/stdc++.h>
using namespace std;
typedef long long ll;

const int N=10004;
vector<int>g[N];
int in[N],n,m,x,d[N];
int topo(){
queue<int>q;
for(int i=1;i<=n;i++){
if(in[i]==0) q.push(i),d[i]=1;
}
int c=0;
while(!q.empty()){
int u=q.front();q.pop();
++c;
for(int v:g[u]){
d[v]=max(d[v],d[u]+1);
if(--in[v]==0) q.push(v);
}
}
if(c!=n) return -1;
else return *max_element(d+1,d+n+1);
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&m);
in[i]=m;
for(int j=1;j<=m;j++){
scanf("%d",&x);
g[x].push_back(i);
}
}
int ans=topo();
printf("%d\n",ans);
}

2、分配资源ID

给定一个管理ID的资源池,可以从资源池中分配资源ID和释放资源ID,分配方式有动会正态分配和指定分配,动态分配是从资源池的开始分配一个资源D,指定分配是指定个资源ID进行分配,无论哪种分配方式释放资源ID时都需要放到资源池的尾部。执行系列操作后,请问资源池的第一个空闲资源ID应该是多少?

注意: 资原池的初始顺序是从小到大。 源池中空闲资源ID不足时,动态分配失败,对资源池不进行任何操作。 指定分配资源ID已经被占用或者不在资源池范围内时,对资源池不进行任何操作。 释放资源ID不在资源池范围内时或者已经是空闲资源ID时,对资源池不进行任何操作。 保证每个用例最后都有空闲资源ID。

输入

第一行是资源池的范围; 第二行是操作个数。 第三行开始,第一个数字代表操作类型,1表示动态分配,2表示指定分配,3表示释放; 如果第一个数字是1,第一个表示分配的个数; 如果第一个数字是2,第二个表示分配的资源ID; 如果第一个数字是3,第二个表示释放的资源ID,

输出

资源池的第一个空闲资源ID。

样例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
输入:
1 3
2
1 1
3 1

输出:
2

提示:第一行资源池范围是[1,3],资源池的初始顺序是1->2->3。
第二行操作个数有2个。
第三行动态分配1个资源ID,资源池中剩余的资源ID顺序是2->3。
有四行释放1个资源ID,资源ID是1,资源池中剩余的资源ID顺序是2->3->1。
执行以上操作后,资源池的第一个空闲资源ID是2。

输入:
1 3
3
2 2
3 2
1 1

输出:
-1

提示:第一行行资源池范围是[1,3],资源池的初始顺序是1->2->3。
第二行操作个数有3个。
第三行指定分配1个资源ID,资源ID是2,资源池中剩余的资源ID顺序是1->3。
第四行释放1个资源ID,资源ID是2,资源池中剩余的资源ID顺序是1->3->2。
第五行动态分配1个资源ID,分配的资源D是1,资源池中剩余的资源ID顺序是3->2。
执行以上操作后,资源池的第一个空闲资源ID是3。

提示

保证输入的操作都是合法的。 操作类型范围是[1,3]。 分配次数范围是[1,100000]。

解答

由于给定的是一个区间,我们将这个区间挪到\([1,n]\)中方便我们进行处理,从而得到一个偏移值。在最终输出答案时,我们需要将这个偏移值加回去即可。

那么我们使用二元组有序集<时间戳,资源ID>来保持这个队列的有序性,并用一个数组pos来标识资源ID目前被释放后的时间戳。通过暴力维护这两个信息从而保证每个资源ID能够被正确地删除即可。整个算法的时间复杂度为\(O(n\log n)\)

这题还可以使用链表解决,不过我对链表操作不熟悉,所以使用set来求解。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# include <bits/stdc++.h>
# define X first
# define Y second
using namespace std;
typedef long long ll;

const int N=100004;
set<pair<int,int>>st;
int pos[N],tot=0;
int main(){
int n,q,o,x,l,r;
scanf("%d %d %d",&l,&r,&q);
n=r-l+1;
for(int i=1;i<=n;i++)
st.insert(make_pair(i,i)),pos[i]=i;
tot=n;
while(q--){
scanf("%d %d",&o,&x);
x-=l-1;
if(o==1){
if(st.size()>=x){
for(int i=0;i<x;i++){
auto pa=*st.begin();
st.erase(pa);
pos[pa.Y]=0;
}
}
}
else if(o==2){
if(1<=x&&x<=n&&pos[x]!=0){
st.erase(make_pair(pos[x],x));
pos[x]=0;
}
}
else{
if(1<=x&&x<=n&&pos[x]==0){
pos[x]=++tot;
st.insert(make_pair(pos[x],x));
}
}
}
printf("%d\n",st.begin()->Y+l-1);
}

3、疯长的草

将种不同的草随机种在一块广漠无垠的二维平面上(直角坐标系内),给定二维数组points表示第0天所有草的初始位置,第i项 points[i]=[xi, yi]表第0天草i在点[xi, yi]。每天,被草覆盖的点会向外蔓延到它上、下,左、右,左上、左下、右上、右下8个邻居点,注意,初始状态下,可能有多种草在同一点上。

现给定一个整数M,问最小需要多少天,方能找到一点同时至少有M种草?

输入

第一行输入整数M。(2<= M <= n) 第二行输入草的种数n。(2 <= n <= 50) 后面连续n行输入草初始位置[xi, yi]。(<= xi, vi <= 10^9)

输出

返口找到一点至少生长M种草的最少天数,找不到返回0

样例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
输入:
2
2
2 1
6 2

输出:
2

提示:在第2天,点(4,0)、(4,1)、(4,2)与(4,3)将有2种草。

输入:
2
3
2 1
6 2
100 100

输出:
2

提示

n = points.length 2 <= n <= 50 points[i].length = 2

解答

如果第\(n\)天已经满足了有\(m\)棵草在同一位置上,那么第\(n+1\)天也必定满足这个条件。这启发我们可以使用二分法解决这个问题。

如何判断第\(d\)天存不存在一个点被覆盖了\(m\)次?我们可以观察到到,第\(m\)天后第\(i\)种草所覆盖的位置是一个正方形,这个正方形的左下角和右上角的坐标分别为\((x_i-d,y_i-d),(x_i+d,y_i+d)\),我们构造出来了\(d\)天后每种草的覆盖情况。

那么为了进行判断,最暴力的方法则是开辟一个二维数组,对于第\(i\)种草,将正方形以内\((x_i-d,y_i-d),(x_i+d,y_i+d)\)的所有点都进行标记一次。这将会导致时间复杂度过大(不过在这题,由于\(n\le 50\),可以忽略这个问题)。为此,我们可以引用二维前缀和进行优化,防止所有点都被遍历\(n\)次。

此外我们还注意到,坐标的范围达到了惊人的\(10^9\),为了能够表示上面这些正方形的边界信息,我们需要对这些坐标进行离散化,才能够完成上述直接在二维数组中进行标记的做法。为了保证边界的正确性,我们使用左闭右开的方式精准表示每个正方形离散化后的边界。

最终,完成本题的时间复杂度为\(O(n^2\log n\cdot \log M)\),其中\(M\)是潜在的最大天数。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# include <bits/stdc++.h>
using namespace std;
typedef long long ll;

const int N=54;
struct P{
int lx,rx,ly,ry;
}g[54];
int x[N],y[N],m,n;
int s[N+N][N+N];
bool ok(int d){
memset(s,0,sizeof(s));
map<int,int>mx,my;
for(int i=1;i<=n;i++){
g[i]=P{x[i]-d,x[i]+d+1,y[i]-d,y[i]+d+1};
mx[g[i].lx]=mx[g[i].rx]=0;
my[g[i].ly]=my[g[i].ry]=0;
}
int r=0,c=0;
for(auto it=mx.begin();it!=mx.end();it++) it->Y=++r;
for(auto it=my.begin();it!=my.end();it++) it->Y=++c;
for(int i=1;i<=n;i++){
int lx=mx[g[i].lx];
int rx=mx[g[i].rx];
int ly=my[g[i].ly];
int ry=my[g[i].ry];
++s[lx][ly];++s[rx][ry];
--s[lx][ry];--s[rx][ly];
}
for(int i=1;i<=r;i++){
for(int j=1;j<=c;j++){
s[i][j]+=s[i-1][j]+s[i][j-1]-s[i-1][j-1];
if(s[i][j]>=m) return 1;
}
}
return 0;
}
int main(){
scanf("%d %d",&m,&n);
for(int i=1;i<=n;i++)
scanf("%d %d",&x[i],&y[i]);
int l=0,r=1e9;
while(l<r){
int mid=(l+r)>>1;
if(ok(mid)) r=mid;
else l=mid+1;
}
printf("%d\n",l);
}
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
Ujimatsu Chiya 微信 微信
Ujimatsu Chiya 支付宝 支付宝