1 条题解
-
0
打孔卡片 题解
题目分析
我们有 n 张打孔卡片,每张卡片有 m 个位置,每个位置要么是字母(不透明),要么是孔(透明)。我们需要将这些卡片按一定顺序叠放,使得从上往下看时,形成的字符串与目标字符串 s 完全一致。
关键规则:
- 对于每个位置 i,看到的字母是最上面那张在该位置有字母的卡片的字母
- 如果某个位置在所有卡片上都是孔,则无法形成目标字符串
解题思路
我们可以将这个问题转化为依赖关系图的拓扑排序问题:
-
依赖关系建立:
- 对于每个位置 i,目标字母是 s[i]
- 找出所有在该位置有字母的卡片
- 将这些卡片分为两类:
- 匹配卡片:字母等于 s[i]
- 不匹配卡片:字母不等于 s[i]
- 所有不匹配卡片必须放在所有匹配卡片的下方
-
图构建优化:
- 为每个位置选择一个匹配卡片作为"顶部卡片"
- 所有不匹配卡片都指向这个顶部卡片
- 这样可以减少边数,避免 O(n²) 的复杂度
-
拓扑排序:
- 构建有向图后,进行拓扑排序
- 如果存在环,说明无解
- 否则输出拓扑序
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)
关键点说明
- 依赖关系建立:核心思想是"不匹配卡片必须放在匹配卡片下方"
- 图构建优化:通过选择代表卡片减少边数,避免暴力建图
- 拓扑排序:使用队列实现,效率高且易于理解
- 环检测:如果拓扑序列长度不等于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
- 上传者