Featured image of post 1024挑战赛题解

1024挑战赛题解

1024 程序员节编程挑战赛题解 - 区间覆盖问题

离开学校出社会工作已久,甚是怀念大学时代 ACM 的时光,偶遇组织于 10.24 举行编程挑战赛,遂前去试了一下,很幸运地也拿了点小奖,后于此简单分享。

题目描述

某客户来到我司,购买了 N 份保单,每份保单都有一个起始时间和终止时间。为了简化计算,这两个时间使用两个整数来表示,取值范围为 0 到 1,000,000,000。

例如,其中一份保单的起始时间 t=3 且终止时间 t=9,那么这份保单的有效区间则覆盖了 6 个单位时间(包含两端点)。

可是,由于个人原因,该客户想退掉其中 1 份保单,求 剩余保单能覆盖的最大单位时间总量 是多少?

输入输出格式

输入文件 input.txt:

  • 第一行是整数 N(0 < N <= 100,000),表示接下来总共有 N 行
  • 接下来的 N 行,每行表示一份保单的起始以及终止时间(以空格分割)
  • 所有终止时间都是不同的

输出文件 output.txt:

  • 输出一个数,表示退保 1 份保单后仍能得到覆盖的最大单位时间总量

示例

输入文件(input.txt):
3
6 9
1 5
3 7

输出文件(output.txt):
7

解释: 三份保单覆盖区间 [1,5]、[3,7]、[6,9]

  • 最大覆盖总量 = (5-1) + (9-6) = 4 + 3 = 7
  • 选择退掉 [3,7](最短独立区间),剩余覆盖量最大

题目分析

要点说明
数据规模最多 10 万个区间
时间范围0 到 1,000,000,000
核心考察区间处理、动态规划思想
关键难点如何找到"最无用"的保单

暴破解法(不可行)

假设分配 10 亿个桶,遍历所有区间… ❌ 数据量太大

正确思路

  1. 每个保单是否"有用",取决于它的独立覆盖区域
  2. 只需计算每个保单独立拥有的区域大小
  3. 剔除独立区域最小的保单即可

解题思路

┌─────────────────────────────────────────────────────────┐
│                      算法流程                            │
├─────────────────────────────────────────────────────────┤
│                                                         │
│  1. 读取 N 个保单,计算每个保单的区间长度                │
│                                                         │
│  2. 按起点、终点递增方式进行二级排序                     │
│                                                         │
│  3. 划分不重叠的大区间(多个重叠取最小&最大值)          │
│                                                         │
│  4. 计算每个保单的独立覆盖区域                           │
│                                                         │
│  5. 找到独立区域最小的保单,剔除                         │
│                                                         │
│  6. 累加剩余保单的独立区域 = 答案                       │
│                                                         │
└─────────────────────────────────────────────────────────┘

C 语言实现

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MAX_N 100000

// 区间结构体
typedef struct {
    int start;
    int end;
    int length;      // 区间长度
    int alone;       // 独立区域
} Interval;

// 比较函数:按起点排序
int cmp_start(const void *a, const void *b) {
    Interval *ia = (Interval *)a;
    Interval *ib = (Interval *)b;
    if (ia->start != ib->start) {
        return ia->start - ib->start;
    }
    return ia->end - ib->end;
}

// 读取输入
int read_input(const char *filename, Interval *intervals) {
    FILE *fp = fopen(filename, "r");
    if (!fp) {
        printf("Cannot open file: %s\n", filename);
        return -1;
    }
    
    int n;
    fscanf(fp, "%d", &n);
    
    for (int i = 0; i < n; i++) {
        fscanf(fp, "%d %d", &intervals[i].start, &intervals[i].end);
        intervals[i].length = intervals[i].end - intervals[i].start;
        intervals[i].alone = 0;
    }
    
    fclose(fp);
    return n;
}

// 计算独立区域
void calculate_alone(Interval *intervals, int n) {
    // 遍历所有区间对
    for (int i = 0; i < n; i++) {
        int start = intervals[i].start;
        int end = intervals[i].end;
        int overlap_start = -1;
        int overlap_end = -1;
        
        // 找到所有与当前区间重叠的区间
        for (int j = 0; j < n; j++) {
            if (i == j) continue;
            
            // 检测重叠:当前区间的起点在另一个区间内
            if (intervals[j].start <= start && start < intervals[j].end) {
                if (overlap_start == -1 || intervals[j].start > overlap_start) {
                    overlap_start = intervals[j].start;
                }
            }
            
            // 检测重叠:当前区间的终点在另一个区间内
            if (intervals[j].start < end && end <= intervals[j].end) {
                if (overlap_end == -1 || intervals[j].end < overlap_end) {
                    overlap_end = intervals[j].end;
                }
            }
        }
        
        // 计算独立区域
        int left_alone = 0;
        int right_alone = 0;
        
        if (overlap_start != -1) {
            left_alone = start - overlap_start;
        } else {
            left_alone = intervals[i].length;
        }
        
        if (overlap_end != -1) {
            right_alone = overlap_end - end;
        } else {
            right_alone = intervals[i].length;
        }
        
        intervals[i].alone = left_alone + right_alone;
    }
}

// 写输出
void write_output(const char *filename, long long result) {
    FILE *fp = fopen(filename, "w");
    if (!fp) {
        printf("Cannot open file: %s\n", filename);
        return;
    }
    
    fprintf(fp, "%lld\n", result);
    fclose(fp);
}

int main() {
    Interval intervals[MAX_N];
    
    // 读取输入
    int n = read_input("input.txt", intervals);
    if (n <= 0) {
        return 1;
    }
    
    // 排序
    qsort(intervals, n, sizeof(Interval), cmp_start);
    
    // 计算每个保单的独立区域
    calculate_alone(intervals, n);
    
    // 找到独立区域最小的
    int min_alone = intervals[0].alone;
    long long total_length = intervals[0].length;
    
    for (int i = 1; i < n; i++) {
        total_length += intervals[i].length;
        if (intervals[i].alone < min_alone) {
            min_alone = intervals[i].alone;
        }
    }
    
    // 结果 = 总长度 - 被剔除区间的独立区域
    long long result = total_length - min_alone;
    
    // 写输出
    write_output("output.txt", result);
    
    printf("Result: %lld\n", result);
    
    return 0;
}

优化版本(单次读入 + 动态内存)

下面的写法与上文算法一致:读入后排序,调用同样的 calculate_alone 逻辑,再把「总区间长度之和 − 最小独立覆盖」写入结果。此处用 malloc/free 取代固定栈上大数组,适合在栈空间受限的环境编译;去掉 C++ 的 new,避免混用两套语言特性。

#include <stdio.h>
#include <stdlib.h>

#define MAX_N 100000

typedef struct {
    int start;
    int end;
    int length;
    int alone;
} Interval;

static int cmp_start(const void *a, const void *b) {
    Interval *ia = (Interval *)a;
    Interval *ib = (Interval *)b;
    return ia->start != ib->start ? ia->start - ib->start : ia->end - ib->end;
}

static void calculate_alone(Interval *intervals, int n) {
    int i, j;
    for (i = 0; i < n; i++) {
        int start = intervals[i].start;
        int end = intervals[i].end;
        int overlap_start = -1;
        int overlap_end = -1;

        for (j = 0; j < n; j++) {
            if (i == j) continue;

            if (intervals[j].start <= start && start < intervals[j].end) {
                if (overlap_start == -1 || intervals[j].start > overlap_start) {
                    overlap_start = intervals[j].start;
                }
            }

            if (intervals[j].start < end && end <= intervals[j].end) {
                if (overlap_end == -1 || intervals[j].end < overlap_end) {
                    overlap_end = intervals[j].end;
                }
            }
        }

        {
            int left_alone, right_alone;
            if (overlap_start != -1) {
                left_alone = start - overlap_start;
            } else {
                left_alone = intervals[i].length;
            }

            if (overlap_end != -1) {
                right_alone = overlap_end - end;
            } else {
                right_alone = intervals[i].length;
            }

            intervals[i].alone = left_alone + right_alone;
        }
    }
}

int main(void) {
    FILE *fin = fopen("input.txt", "r");
    FILE *fout = fopen("output.txt", "w");
    int n, i;

    if (!fin || !fout) return 1;

    fscanf(fin, "%d", &n);
    if (n <= 0 || n > MAX_N) {
        fclose(fin);
        fclose(fout);
        return 1;
    }

    Interval *intervals = (Interval *)malloc(sizeof(Interval) * (size_t)n);
    if (!intervals) {
        fclose(fin);
        fclose(fout);
        return 1;
    }

    long long total_length = 0;

    for (i = 0; i < n; i++) {
        fscanf(fin, "%d %d", &intervals[i].start, &intervals[i].end);
        intervals[i].length = intervals[i].end - intervals[i].start;
        intervals[i].alone = intervals[i].length;
        total_length += intervals[i].length;
    }
    fclose(fin);

    qsort(intervals, (size_t)n, sizeof(Interval), cmp_start);
    calculate_alone(intervals, n);

    int min_alone = intervals[0].alone;
    for (i = 1; i < n; i++) {
        if (intervals[i].alone < min_alone) {
            min_alone = intervals[i].alone;
        }
    }

    {
        long long result = total_length - min_alone;
        fprintf(fout, "%lld\n", result);
        printf("Result: %lld\n", result);
    }

    fclose(fout);
    free(intervals);
    return 0;
}

说明:比赛场景下也可继续尝试「扫描线 / 线段树」等更精细的区间结构以降低常数;本文侧重与前一版一致的思路对照。

© 2016-2026 Yison. All rights reserved.
使用 Hugo 构建
主题 StackJimmy 设计