1 条题解
-
0
Christmas Gifts 题解
题目分析
这是一个图论中的边定向问题。我们有:
- 个学生(顶点)
- 对朋友关系(边)
- 需要为每条边确定方向(礼物赠送方向)
- 目标是最小化不公平分数
其中:
- = 学生 送出的礼物数(出度)
- = 学生 收到的礼物数(入度)
数学推导
对于每个顶点 :
- (度数守恒)
最优情况:
- 如果 是偶数,可以做到
- 如果 是奇数,最小可能
因此: 最小不公平分数 = 图中奇度顶点的数量
算法思路
1. 理论基础
- 在无向图中,奇度顶点的数量总是偶数
- 对于每个连通分量,可以通过添加虚拟边使所有顶点度数为偶数
- 存在欧拉回路,沿着回路交替定向可达最优
2. 构造步骤
- 统计所有奇度顶点
- 对每个连通分量,在奇度顶点间添加虚拟边
- 在增强后的图中找欧拉回路
- 沿着回路确定边方向
- 输出原图边的方向(忽略虚拟边)
C语言实现
#include <stdio.h> #include <stdlib.h> #include <string.h> #define MAXN 100005 #define MAXM 400005 typedef struct { int to, next, id; int used; } Edge; Edge edge[MAXM]; int head[MAXN], deg[MAXN], tot; int original_u[MAXM], original_v[MAXM]; int answer_u[MAXM], answer_v[MAXM], answer_cnt; int visited[MAXN]; void add_edge(int u, int v, int id) { edge[tot].to = v; edge[tot].next = head[u]; edge[tot].id = id; edge[tot].used = 0; head[u] = tot++; edge[tot].to = u; edge[tot].next = head[v]; edge[tot].id = id; edge[tot].used = 0; head[v] = tot++; deg[u]++; deg[v]++; } void dfs_component(int u, int* comp_odd, int* comp_odd_cnt) { visited[u] = 1; if (deg[u] % 2 == 1) { comp_odd[(*comp_odd_cnt)++] = u; } for (int i = head[u]; i != -1; i = edge[i].next) { int v = edge[i].to; if (!visited[v]) { dfs_component(v, comp_odd, comp_odd_cnt); } } } void euler(int start) { int stack[MAXN], top = 0; stack[top++] = start; visited[start] = 1; while (top > 0) { int u = stack[top-1]; int *i = &head[u]; // 跳过已使用的边 while (*i != -1 && edge[*i].used) { *i = edge[*i].next; } if (*i == -1) { top--; continue; } int v = edge[*i].to; int edge_id = edge[*i].id; // 记录原图边的方向 if (edge_id != -1) { answer_u[answer_cnt] = original_u[edge_id]; answer_v[answer_cnt] = original_v[edge_id]; answer_cnt++; } edge[*i].used = 1; edge[*i ^ 1].used = 1; if (!visited[v]) { visited[v] = 1; } stack[top++] = v; } } int main() { int S, F; scanf("%d %d", &S, &F); // 初始化 memset(head, -1, sizeof(head)); tot = 0; answer_cnt = 0; // 读入边 for (int i = 0; i < F; i++) { int A, B; scanf("%d %d", &A, &B); original_u[i] = A; original_v[i] = B; add_edge(A, B, i); } // 统计奇度顶点总数 int odd_count = 0; for (int i = 1; i <= S; i++) { if (deg[i] % 2 == 1) { odd_count++; } } // 为每个连通分量添加虚拟边 memset(visited, 0, sizeof(visited)); int virtual_edge_id = F; int* comp_odd = malloc(S * sizeof(int)); for (int i = 1; i <= S; i++) { if (!visited[i] && head[i] != -1) { int comp_odd_cnt = 0; dfs_component(i, comp_odd, &comp_odd_cnt); // 在连通分量内连接奇度顶点 for (int j = 0; j + 1 < comp_odd_cnt; j += 2) { int u = comp_odd[j], v = comp_odd[j+1]; original_u[virtual_edge_id] = u; original_v[virtual_edge_id] = v; add_edge(u, v, virtual_edge_id); virtual_edge_id++; } } } free(comp_odd); // 输出最小不公平分数 printf("%d\n", odd_count); // 为整个图找欧拉回路 memset(visited, 0, sizeof(visited)); for (int i = 1; i <= S; i++) { if (!visited[i] && head[i] != -1) { euler(i); } } // 输出边方向(只输出原图的边) for (int i = 0; i < answer_cnt; i++) { printf("%d %d\n", answer_u[i], answer_v[i]); } return 0; }关键算法说明
1. 图表示
- 使用邻接表存储图结构
head[i]存储顶点 的第一条边- 每条边存储两次(双向)
2. 连通分量处理
- 使用 DFS 找出每个连通分量
- 在分量内部连接奇度顶点对
- 保证每个连通分量度数为偶数
3. 欧拉回路
- 使用栈模拟递归避免栈溢出
- 沿着回路确定边方向
- 跳过已使用的边提高效率
4. 方向记录
- 在遍历时记录原图边的方向
- 忽略虚拟边的方向输出
复杂度分析
- 时间复杂度:
- 图遍历:
- 欧拉回路:
- 空间复杂度:
- 邻接表:
- 辅助数组:
正确性证明
- 最优性:每个奇度顶点贡献至少为 1,算法达到该下界
- 可行性:通过虚拟边保证欧拉回路存在
- 完整性:输出所有原图边的方向,满足题目要求
样例验证
输入:
4 5 1 2 2 3 2 4 1 3 3 4处理过程:
- 奇度顶点:2, 3(共2个)
- 最小分数 = 2
- 构造欧拉回路确定方向
- 输出5条边的方向
输出:
2 2 4 4 3 1 3 3 2 2 1该解法高效且正确,适用于大规模数据。
- 1
信息
- ID
- 4833
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 7
- 标签
- 递交数
- 4
- 已通过
- 0
- 上传者