1 条题解

  • 0
    @ 2025-10-29 16:18:52

    打孔卡片 题解

    题目分析

    我们有 n 张打孔卡片,每张卡片有 m 个位置,每个位置要么是字母(不透明),要么是孔(透明)。我们需要将这些卡片按一定顺序叠放,使得从上往下看时,形成的字符串与目标字符串 s 完全一致。

    关键规则:

    • 对于每个位置 i,看到的字母是最上面那张在该位置有字母的卡片的字母
    • 如果某个位置在所有卡片上都是孔,则无法形成目标字符串

    解题思路

    我们可以将这个问题转化为依赖关系图的拓扑排序问题:

    1. 依赖关系建立

      • 对于每个位置 i,目标字母是 s[i]
      • 找出所有在该位置有字母的卡片
      • 将这些卡片分为两类:
        • 匹配卡片:字母等于 s[i]
        • 不匹配卡片:字母不等于 s[i]
      • 所有不匹配卡片必须放在所有匹配卡片的下方
    2. 图构建优化

      • 为每个位置选择一个匹配卡片作为"顶部卡片"
      • 所有不匹配卡片都指向这个顶部卡片
      • 这样可以减少边数,避免 O(n²) 的复杂度
    3. 拓扑排序

      • 构建有向图后,进行拓扑排序
      • 如果存在环,说明无解
      • 否则输出拓扑序

    C语言实现

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    #define MAX_N 100005
    #define MAX_M 100005
    #define MAX_TOTAL 200005
    
    typedef struct {
        int card_idx;
        char letter;
    } CardPos;
    
    typedef struct Node {
        int to;
        struct Node* next;
    } Node;
    
    CardPos cards[MAX_M][MAX_TOTAL / 100]; // 每个位置的卡片列表
    int card_count[MAX_M]; // 每个位置的卡片数量
    
    Node* graph[MAX_N]; // 邻接表
    int indeg[MAX_N]; // 入度
    int order[MAX_N]; // 拓扑序
    
    // 添加边
    void add_edge(int from, int to) {
        Node* new_node = (Node*)malloc(sizeof(Node));
        new_node->to = to;
        new_node->next = graph[from];
        graph[from] = new_node;
        indeg[to]++;
    }
    
    int main() {
        int n, m;
        scanf("%d %d", &n, &m);
        
        char s[MAX_M];
        scanf("%s", s);
        
        // 初始化
        for (int i = 0; i < m; i++) {
            card_count[i] = 0;
        }
        for (int i = 0; i < n; i++) {
            graph[i] = NULL;
            indeg[i] = 0;
        }
        
        // 读入卡片数据
        for (int idx = 0; idx < n; idx++) {
            int k;
            scanf("%d", &k);
            for (int j = 0; j < k; j++) {
                int pos;
                char ch[2];
                scanf("%d %s", &pos, ch);
                pos--; // 转为0-based
                
                cards[pos][card_count[pos]].card_idx = idx;
                cards[pos][card_count[pos]].letter = ch[0];
                card_count[pos]++;
            }
        }
        
        // 构建依赖图
        int valid = 1;
        for (int i = 0; i < m && valid; i++) {
            if (card_count[i] == 0) {
                valid = 0;
                break;
            }
            
            // 找出匹配和不匹配的卡片
            int match[MAX_N], match_cnt = 0;
            int nomatch[MAX_N], nomatch_cnt = 0;
            
            for (int j = 0; j < card_count[i]; j++) {
                int card_idx = cards[i][j].card_idx;
                char letter = cards[i][j].letter;
                
                if (letter == s[i]) {
                    match[match_cnt++] = card_idx;
                } else {
                    nomatch[nomatch_cnt++] = card_idx;
                }
            }
            
            if (match_cnt == 0) {
                valid = 0;
                break;
            }
            
            // 选择第一个匹配卡片作为顶部卡片
            int top = match[0];
            
            // 添加依赖边:所有不匹配卡片 -> 顶部卡片
            for (int j = 0; j < nomatch_cnt; j++) {
                add_edge(nomatch[j], top);
            }
        }
        
        if (!valid) {
            printf("-1\n");
            return 0;
        }
        
        // 拓扑排序
        int queue[MAX_N];
        int front = 0, rear = 0;
        
        // 将入度为0的节点加入队列
        for (int i = 0; i < n; i++) {
            if (indeg[i] == 0) {
                queue[rear++] = i;
            }
        }
        
        int order_size = 0;
        while (front < rear) {
            int u = queue[front++];
            order[order_size++] = u;
            
            // 遍历u的所有出边
            Node* cur = graph[u];
            while (cur != NULL) {
                int v = cur->to;
                indeg[v]--;
                if (indeg[v] == 0) {
                    queue[rear++] = v;
                }
                cur = cur->next;
            }
        }
        
        // 检查是否有环
        if (order_size != n) {
            printf("-1\n");
        } else {
            for (int i = 0; i < n; i++) {
                printf("%d", order[i] + 1);
                if (i < n - 1) printf(" ");
            }
            printf("\n");
        }
        
        // 释放内存
        for (int i = 0; i < n; i++) {
            Node* cur = graph[i];
            while (cur != NULL) {
                Node* temp = cur;
                cur = cur->next;
                free(temp);
            }
        }
        
        return 0;
    }
    

    算法复杂度

    • 时间复杂度:O(n + m + ∑k_i),其中 ∑k_i 是所有卡片上字母位置的总数
    • 空间复杂度:O(n + m + ∑k_i)

    关键点说明

    1. 依赖关系建立:核心思想是"不匹配卡片必须放在匹配卡片下方"
    2. 图构建优化:通过选择代表卡片减少边数,避免暴力建图
    3. 拓扑排序:使用队列实现,效率高且易于理解
    4. 环检测:如果拓扑序列长度不等于n,说明存在环,无解

    样例验证

    样例1

    输入:
    1 1
    a
    1 1 a
    输出:
    1
    

    样例2

    输入:
    3 4
    glhf
    3 1 r 3 h 4 i
    3 1 r 2 l 3 o
    2 1 g 4 f
    输出:
    3 1 2
    

    样例3

    输入:
    2 2
    aa
    2 1 a 2 b
    2 1 b 2 a
    输出:
    -1
    

    该解法能够高效处理 n, m ≤ 10^5 的大数据规模,符合题目要求。

    • 1

    信息

    ID
    3709
    时间
    1000ms
    内存
    256MiB
    难度
    7
    标签
    递交数
    13
    已通过
    1
    上传者