BUAA 计组 26 年上机题(更新中)

P0,P1 当时觉得太宝宝巴士了,所以没记。P2 感觉上了一点强度,而且 P3 考前想找往年题最后只找到了 \(6\) 道,所以觉得还是记录一下让后人乘凉吧。

这里推销一下我和一个学长共同开发的 VS Code 插件:BUAA CO Toolkit,波形图比 ISE 好看很多哦。

P2

P2 的话注意利用好宏,我进考场第一件事就是敲了五六个宏。

T1

给定一个 0 和 1 构成的 \(n\times m\) 的网络,1 代表陆地, 0 代表水域,求所有陆地的总周长。

周长定义为:

对于一个陆地格,它的每条边按以下规则计算:

  • 如果边的另一侧是水域,该边对总周长贡献。

  • 如果边的另一侧超出网格范围,该边对总周长贡献。

  • 如果边的另一侧也是陆地,该边不计入总周长。

网格可以包含多个岛屿,输出所有陆地的总周长即可。陆地内部被包围的水域与陆地之间的边界也计入总周长。


这题没有参考的 C 语言代码。

第一题实际上就比较有强度了,我考场上写了很多 if,注意判断边界然后标签太多注意命名格式,我是采用 loop_<第一层标号>_<第二层标号>... 这种类似的命名格式的。然后注意循环之后要自增和跳回!一般来说循环有两类写法:

Loop:
	...
	beq $t0, $t1, Exit
	j Loop
Exit:

和

Loop:
	...
	bne $t0, $t1, Loop

后者当然不会忘记跳回,而且实际上能少一步,前者可能会忘记。但值得一提的是这两者的逻辑是不一样的,前者类似 while 后者类似 do while,还是要做区分。

最后的代码:

Code
.data
    Grid: .space 2000
    
.macro done
    li $v0, 10
    syscall
.end_macro

.macro input(%reg)
    li $v0, 5
    syscall
    move %reg, $v0
.end_macro

.macro get_pos(%pos, %x, %y)
    move %pos, %x
    mult %pos, $s1
    mflo %pos
    add %pos, %pos, %y
.end_macro

.text
    input($s0)
    input($s1)
    li $t0, 0
    loop_1:
        li $t1, 0
        loop_1_1:
            get_pos($t2, $t0, $t1)
            sll $t2, $t2, 2
            input($t3)
            sw $t3, Grid($t2)
            addi $t1, $t1, 1
            blt $t1, $s1, loop_1_1
        addi $t0, $t0, 1
        blt $t0, $s0, loop_1
    
    li $s2, 0
    li $t0, 0
    loop_2:
        li $t1, 0
        loop_2_1:
            get_pos($t2, $t0, $t1)
            sll $t2, $t2, 2
            lw $t2, Grid($t2)
            beq $t2, $0, if_end
            addi $t5, $t0, -1

            blt $t5, 0, if_1
                get_pos($t3, $t5, $t1)
                
                sll $t3, $t3, 2
                lw $t3, Grid($t3)
                bne $t3, $0, if_1_end
                addi $s2, $s2, 1
                j if_1_end
            if_1:
                addi $s2, $s2, 1
            if_1_end:

            addi $t5, $t0, 1

            bge $t5, $s0, if_2
                get_pos($t3, $t5, $t1)
                
                sll $t3, $t3, 2
                lw $t3, Grid($t3)
                bne $t3, $0, if_2_end
                addi $s2, $s2, 1
                j if_2_end
            if_2:
                addi $s2, $s2, 1
            if_2_end:

            addi $t6, $t1, -1
            
            blt $t6, 0, if_3
                get_pos($t3, $t0, $t6)
                
                sll $t3, $t3, 2
                lw $t3, Grid($t3)
                bne $t3, $0, if_3_end
                addi $s2, $s2, 1
                j if_3_end
            if_3:
                addi $s2, $s2, 1
            if_3_end:
            
            addi $t6, $t1, 1

            bge $t6, $s1, if_4
                get_pos($t3, $t0, $t6)
                
                sll $t3, $t3, 2
                lw $t3, Grid($t3)
                bne $t3, $0, if_4_end
                addi $s2, $s2, 1
                j if_4_end
            if_4:
                addi $s2, $s2, 1
            if_4_end:

            if_end:
            addi $t1, $t1, 1
            bne $t1, $s1, loop_2_1
        addi $t0, $t0, 1
        bne $t0, $s0, loop_2
    li $v0, 1
    move $a0, $s2
    syscall
    done

T2

根据前序遍历序列和中序遍历序列写出后序遍历,这题哪怕放 DS 期末估计都要斩杀一片人吧,给出了 C 语言代码,不过第二题就考递归吗,还是太难了。

参考代码
#include <stdio.h>

#define MAXN 205

int n;
int pre[MAXN];
int in[MAXN];

void build(int pl, int pr, int il, int ir) {
    if (pl > pr) {
        return;
    }

    int root = pre[pl];

    // Locate the root in the inorder sequence
    int k = il;
    while (in[k] != root) {
        k++;
    }

    int leftSize = k - il;

    // Left subtree
    build(pl + 1, pl + leftSize, il, k - 1);
    // Right subtree
    build(pl + leftSize + 1, pr, k + 1, ir);

    printf("%d\n", root);
}

int main() {
    scanf("%d", &n);

    for (int i = 0; i < n; i++) {
        scanf("%d", &pre[i]);
    }
    for (int i = 0; i < n; i++) {
        scanf("%d", &in[i]);
    }

    build(0, n - 1, 0, n - 1);

    return 0;
}

实现要点:注意循环变量也得入栈,注意函数参数也得入栈,注意在函数参数调用前就给他入栈,好了上代码:

Code
.data
    pre: .space 820
    in: .space 820
    space: .asciiz " "
    enter: .asciiz "\n"

.macro space
    la $a0, space
    li $v0, 4
    syscall
.end_macro

.macro endl
    la $a0, enter
    li $v0, 4
    syscall
.end_macro

.macro input(%reg)
    li $v0, 5
    syscall
    move %reg, $v0
.end_macro

.macro done
    li $v0, 10
    syscall
.end_macro

.macro push(%arg)
    add $sp, $sp, -4
    sw %arg, 0($sp)
.end_macro

.macro pop(%arg)
    lw %arg, 0($sp)
    add $sp, $sp, 4
.end_macro

.text
    input($s0)
    li $t0, 0
    for1:
        input($t1)
        sll $t0, $t0, 2
        sw $t1, pre($t0)
        srl $t0, $t0, 2
        addi $t0, $t0, 1
        bne $t0, $s0, for1
    
    li $t0, 0
    for2:
        input($t1)
        sll $t0, $t0, 2
        sw $t1, in($t0)
        srl $t0, $t0, 2
        addi $t0, $t0, 1
        bne $t0, $s0, for2
        
    push($ra)
    push($a0)
    push($a1)
    push($a2)
    push($a3)
    li $a0, 0
    subi $a1, $s0, 1
    li $a2, 0
    subi $a3, $s0, 1
    jal build
    nop
    pop($a3)
    pop($a2)
    pop($a1)
    pop($a0)
    pop($ra)
    done
    
build:
    push($t0)
    push($t1)
    push($t2)
    bgt $a0, $a1, return
    
    sll $t0, $a0, 2
    lw $t0, pre($t0)
    
    move $t1, $a2
    loop:
        sll $t1, $t1, 2
        lw $t2, in($t1)
        srl $t1, $t1, 2
        beq $t2, $t0, loop_end
        addi $t1, $t1, 1
        j loop
    loop_end:
    nop
        
    sub $t2, $t1, $a2
    
    push($ra)
    push($a0)
    push($a1)
    push($a2)
    push($a3)
    add $a1, $a0, $t2
    addi $a0, $a0, 1
    subi $a3, $t1, 1
    jal build
    nop
    pop($a3)
    pop($a2)
    pop($a1)
    pop($a0)
    pop($ra)
    
    push($ra)
    push($a0)
    push($a1)
    push($a2)
    push($a3)
    addi $a0, $a0, 1
    add $a0, $a0, $t2
    addi $a2, $t1, 1
    jal build
    nop
    pop($a3)
    pop($a2)
    pop($a1)
    pop($a0)
    pop($ra)
    
    push($a0)
    li $v0, 1
    move $a0, $t0
    syscall
    pop($a0)
    
    push($a0)
    endl
    pop($a0)
    return:
    pop($t2)
    pop($t1)
    pop($t0)
    jr $ra

注意 build 函数内的循环必须是 while,我考场上把 do while 改 while 的时候忘记 j loop 了,硬控一小时。

写这篇 blog 的时候一样被卡了一小时

T3

简要题意:计算将数组的各个分组分别变成回文序列所需的最少删除次数。

分组方式是给定一个 \(k\),然后定义等价类为 \([r] = \{r+kt|1\le r+kt \le n, k\in \mathbb{Z},1\le r\le n\}\)。

这题也给出了参考代码,但是参考代码的写法调用了两层函数,这还是太恶心了,而且区间 DP 的写法非常蛋疼,需要先枚举 len;于是我的写法把第一个函数写进 main 了。

参考代码
#include <stdio.h>
#define MAX_N 30

int minPalindromeDeletes(const int *subseq, int m) {
    if (m <= 1) return 0;
    int dp[MAX_N][MAX_N];
    for (int i = 0; i < m; i++) dp[i][i] = 0;

    for (int len = 2; len <= m; len++) {
        for (int l = 0; l + len <= m; l++) {
            int r = l + len - 1;
            if (subseq[l] == subseq[r]) {
                dp[l][r] = (len == 2) ? 0 : dp[l + 1][r - 1];
            } else {
                int a = dp[l + 1][r];
                int b = dp[l][r - 1];
                dp[l][r] = (a < b ? a : b) + 1;
            }
        }
    }
    return dp[0][m - 1];
}

int kPalindrome(const int *arr, int n, int k) {
    int ans = 0;
    int subseq[MAX_N];
    for (int r = 0; r < k; r++) {
        int m = 0;
        for (int j = r; j < n; j += k) {
            subseq[m++] = arr[j];
        }
        ans += minPalindromeDeletes(subseq, m);
    }
    return ans;
}

int main(void) {
    int n, k, arr[MAX_N];
    scanf("%d", &n);
    for (int i = 0; i < n; i++) scanf("%d", &arr[i]);
    scanf("%d", &k);
    printf("%d\n", kPalindrome(arr, n, k));
    return 0;
}

注意 Flatten 的时候要把横轴最长长度传进去,你可以直接传 MAX_N 也可以传每次都在变的 m。推荐选前者,因为后者会让你突然想起来你传的其实是 n。不过这题其实没啥太大问题,因为 \(m < n\),就是内存会不连续。

然后注意在调用第二个函数的时候不要偷懒,寄存器有点不太够用,你得把主函数的循环变量也压栈。

最后注意两个 if 块内的三目运算符,这样其实有四个 if 块,既然你都是肉编器了那还是优化一下写法,给这四个 if 块设计一个统一出口,这也符合 C 语言中 goto 语句的使用规范

Code
.data
    arr: .space 240
    subsq: .space 240
    dp: .space 4000
    
.macro input(%reg)
    li $v0, 5
    syscall
    move %reg, $v0
.end_macro

.macro push(%arg)
    add $sp, $sp, -4
    sw %arg, 0($sp)
.end_macro

.macro pop(%arg)
    lw %arg, 0($sp)
    add $sp, $sp, 4
.end_macro

.macro done
    li $v0, 10
    syscall
.end_macro

.macro get_pos(%pos, %x, %y, %m)
    move %pos, %x
    mult %pos, %m
    mflo %pos
    add %pos, %pos, %y
.end_macro
.text
    input($s0)
    li $t0, 0
    loop1:
        sll $t0, $t0, 2
        input($t1)
        sw $t1, arr($t0)
        srl $t0, $t0, 2
        addi $t0, $t0, 1
        bne $t0, $s0, loop1
    input($s1)
#kPalindrome
    li $s2, 0
    li $t0, 0
    loop2:
        li $t1, 0
        move $t2, $t0
        loop21:
            sll $t2, $t2, 2
            lw $t3, arr($t2)
            sw $t3, subsq($t1)
            addi $t1, $t1, 4
            srl $t2, $t2, 2
            add $t2, $t2, $s1
            blt $t2, $s0, loop21
        push($ra)
        push($a0)
        push($t0)
        push($t1)
        srl $t1, $t1, 2
        move $a0, $t1
        jal minPalindromeDeletes
        pop($t1)
        pop($t0)
        pop($a0)
        pop($ra)
        add $s2, $s2, $v0
        addi $t0, $t0, 1
        bne $t0, $s1, loop2	
    
    move $a0, $s2
    li $v0, 1
    syscall
    done

minPalindromeDeletes:
    li $v0, 0
    ble $a0, 1, return
    li $t0, 0
    for_1:
        get_pos($t1, $t0, $t0, $a0)
        sll $t1, $t1, 2
        sw $0, dp($t1)
        addi $t0, $t0, 1
        bne $t0, $a0, for_1
    
    li $t0, 2
    for2:
        li $t1, 0
        for21:
            add $t2, $t1, $t0
            subi $t2, $t2, 1
            sll $t1, $t1, 2
            sll $t2, $t2, 2
            lw $t4, subsq($t1)
            lw $t5, subsq($t2)
            srl $t1, $t1, 2
            srl $t2, $t2, 2
            get_pos($t6, $t1, $t2, $a0)
            bne $t4, $t5, else1
                li $t4, 2
                beq $t0, $t4, else12
                    addi $t1, $t1, 1
                    subi $t2, $t2, 1
                    get_pos($t4, $t1, $t2, $a0)
                    addi $t1, $t1, -1
                    subi $t2, $t2, -1
                    sll $t4, $t4, 2
                    sll $t6, $t6, 2
                    lw $t5, dp($t4)
                    sw $t5, dp($t6)
                    srl $t4, $t4, 2
                    srl $t6, $t6, 2
                    j if_end
                else12:
                    sll $t6, $t6, 2
                    sw $0, dp($t6)
                    srl $t6, $t6, 2
                    j if_end
            else1:
                addi $t1, $t1, 1
                get_pos($t4, $t1, $t2, $a0)
                addi $t1, $t1, -1
                
                subi $t2, $t2, 1
                get_pos($t5, $t1, $t2, $a0)
                subi $t2, $t2, -1
                
                sll $t4, $t4, 2
                sll $t5, $t5, 2
                sll $t6, $t6, 2
                lw $t4, dp($t4)
                lw $t5, dp($t5)
                
                blt $t4, $t5, assigna
                    addi $t5, $t5, 1
                    sw $t5, dp($t6)
                    j if_end
                assigna:
                    addi $t4, $t4, 1
                    sw $t4, dp($t6)
            if_end:
            addi $t1, $t1, 1
            add $t3, $t1, $t0
            ble $t3, $a0, for21
        addi $t0, $t0, 1
        ble $t0, $a0, for2
    sub $t0, $a0, 1
    get_pos($t1, $0, $t0, $a0)
    sll $t1, $t1, 2
    lw $v0, dp($t1)
    return:
        jr $ra

如果这题 C 语言写法换成记忆化搜索那将绝杀

P3

P3 的难度突然骤降了,搞得我都不想更新了。

一进机房我发现所有电脑上都开着虚拟机,我还以为魔改版的 MIPS 只能在 Linux 环境下跑,于是我看了下环境文档,结果啥也没有。

P.S. 据同学反映此次课下的 IM 指令范围规格好像和考试不太一样,所以尽可能开到 ROM 的最大位数吧。

此外写测试脚本的时候注意你的 CPU 没有实现 addiu,所以不要写 li,赋值时用 ori。

T1

题目大意是把 GPR[rs] 和 GPR[rt] 看作两个 01 串,求它们的 \(\operatorname{Hamming}\) 距离并存入 GPR[rd]。

以防有同学不知道 \(\operatorname{Hamming}\) 距离,我还是写一下 RTL:

temp <- GPR[rs] xor GPR[rt]
GPR[rd] <- temp_0 + temp_1 + ... + temp_{31}

注意 Logisim 中 BitAdder 的使用,这是一个可以求这个数字内 \(1\) 的个数的元件。R 型指令还是挺简单的。

T2

名字是 lcbeq(Load Compare Branch if Equal),大概就是魔改 beq,描述如下:

ext_offset <- (sign_extend(offset) << 2)_{31...0}
addr <- GPR[rs] + ext_offset
temp <- Mem[addr]_{31...0}
if temp == GPR[rt]
    PC <- PC + 4 + ext_offset
else
    PC <- PC + 4

乍一看似乎需要改 ALU 的输入端,但其实不能这样,会破坏我们原本的数据流。我的做法是在 DM 下游增加一个 lcbeq_en 模块,执行判断逻辑,然后将这个模块的输出和 Zero 或一下。注意修改一下 NPC,ext_offset 的拓展方式和原本的方式有细微区别。剩下的工作就是改 ALU 里 addr 的计算。

T3

名字是 lwbr,这题算是个奇奇怪怪的 R 型指令,要改的东西蛮多的,描述如下:

addr <- GPR[rs] + GPR[rt]
temp <- Mem[addr]
if temp == 0
    PC <- GPR[rd]
else
    GPR[rd] <- tmp

初看这条指令觉得很简单,仔细一想有两个卡点:

  • 如果你让 GRF 的两个读入口接 rs 和 rt,那 temp == 0 时 PC 的数据来源 GPR[rd] 就读不到。因此应该增加一个 GRF 读入端口。
  • NPC 似乎要新加入 GPR[rd] 的读入端口,这样似乎要增大 NPC_Sel 的位宽(当然如果你是奇奇怪怪的写法当我没说),会非常麻烦,所以我的办法是在 GRF(原本是 jr,jalr 指令时读 GPR[rs] 的端口)上游接一个 MUX,然后选择信号由 DM 下游的 lwbr_check 统一输出。

这时候奇异写法的优势就体现出来了,同学们可以学学 winrs 学长的绿框写法。

对于第一个卡点由于我的 GRF 封装了一个 reg,reg 用来存放我的寄存器池,因此我加指令只需要添加一个 reg,但有的同学就没这么方便了。

然后 T3 是唯一一道我没有一遍过的,因为 T3 我这边涉及到六个信号的修改,而我没有统一给 lwbr_check 的使能信号 lwbr_en 赋初值。这时候就要讲一种赋初值的好办法了——高阻电阻阻塞:

image

posted @ 2026-11-01 22:48  redproblemdog  阅读(12)  评论(0)    收藏  举报