P1047 校门外的树 某校大门外长度为L的马路上有一排树,每两棵相邻的树之间的间隔都是11米。我们可以把马路看成一个数轴,马路的一端在数轴00的位置,另一端在LL的位置;数轴上的每个整数点,即0,1...

本文展示了一个使用C语言处理数组的示例程序,通过读取用户输入的两个整数L和M,以及M组区间数据,将这些区间内的数组元素设置为特定值,最后统计并输出未被设置的元素数量。
#include<stdio.h>  
  
int main()  
{  
    int L, M, i, j, n;  
    int a[10001], b[10001];  
    scanf("%d %d",&L, &M);   //输入L和M  
    n = M*2;      //循环输入b数组0~n的数据  
    for(i=0; i<n; i+=2)  
    {  
        scanf("%d %d", &b[i], &b[i+1]);  
    }  
    for(i=0; i<=L; i++)     //循环给a数组L个元素赋值  
    {  
        a[i] = i;  
    }  
    int r, s;  
    for(i=0; i<n; i+=2)  //遍历访问数组b的各个区间  
    {  
        r = b[i];   //区间起始点  
        s = b[i+1]; //区间终点  
        for(j=r; j<=s; j++) //把数组b各个区间内元素在数组a中映射为0  
        {  
            a[j] = -1;  
        }  
    }  
    int k=0;    //k用来统计不是0的个数,k赋值为1(数组a中原本包含一个0,此时加上)  
    for(i=0; i<=L; i++)  
    {  
        if(a[i] != -1)  
        {  
            k++;    //用来统计非0的个数,即不在数组b区间内的数的个数  
        }  
    }  
    printf("%d", k);  
  
    return 0;  
} 

转载于:https://www.cnblogs.com/Tristan-Adams/p/9862805.html

<think>我们已知:马路长度L(因此有0到L共L+1棵),和M个区域,每个区域由起始点start和终止点end表示(都是整数,且区域可能有重叠)。 目标:计算移走所有区域内的(包括端点)后,马路上剩余的的数量。 思路: 1. 我们可以用一个长度为L+1的数组(下标0到L)来表示的状态,初始化为1(表示存在)。 2. 对于每个区域,我们将这个区域内的所有位置(从start到end,包括端点)的数组值置为0(表示移走)。 3. 遍历整个数组,统计值为1的个数,即为剩余的的数量。 但是注意:区域可能有重叠,所以如果同一个位置被多个区域覆盖,我们只需要置0一次即可(重复置0不影响结果,因为都是置0)。 然而,上述方法在L很大而区域很多且每个区域覆盖范围大时,可能会因为双重循环而效率较低(内循环遍历每个区域内的每个点)。我们可以考虑优化,比如先合并区间,然后再计算被覆盖的总长度,从而用总数减去被覆盖的的数量。但是,由于题目要求用C语言实现,且L和M的范围在题目中未明确,我们这里先用直接模拟的方法(假设L和M都不太大)。 根据引用[4]中给出的代码,也是采用模拟的方法。我们参考其思路,但需要注意: - 数组大小:的数组大小为l+1,区域数组大小为m*2(存储m个区域的起始和终止)。 - 输入:先输入L和M,然后输入M组区域的起始和终止坐标。 但是,引用[4]的代码有一个问题:当L很大(比如1000000)而M也很大(比如10000)且每个区域覆盖范围很大时,双重循环会导致效率低下(最坏情况下,内循环可能达到L,总操作次数M*L,可能达到10^10,在C语言中可能会超时)。 因此,我们可以使用更高效的方法:区间合并,然后计算覆盖的总长度。 高效方法步骤: 1. 读取L和M。 2. 读取M个区间,每个区间为[start, end]。 3. 合并所有区间(因为区间可能重叠,合并后得到不重叠的区间集合)。 4. 计算合并后所有区间的总覆盖长度:总长度 = 每个区间的(end - start + 1)之和。 5. 剩余的数量 = 总数(L+1) - 总覆盖长度。 但是,合并区间需要先对区间按起点排序,然后合并。 两种方法的选择: 如果题目中L和M的范围较小(比如L<=10000, M<=100),那么双重循环的模拟方法可行。 如果范围较大,则应该使用合并区间的方法。 由于题目没有给出范围,为了通用性,我们采用合并区间的方法。 具体步骤(合并区间法): 1. 输入L和M。 2. 定义一个结构体数组来存储区间(大小为M)。 3. 输入M个区间,并存储。 4. 对区间数组按起始点从小到大排序。 5. 合并区间:遍历区间数组,如果当前区间与下一个区间有重叠,则合并(更新结束点为较大值);如果没有重叠,则当前区间合并完成,加入结果区间数组,并继续下一个。 6. 计算合并后所有区间的总长度每个区间长度=end-start+1,然后求和)。 7. 剩余 = (L+1) - 总长度。 注意:合并区间时,需要处理区间重叠的情况。 例如:区间[1,3]和[2,5]重叠,合并为[1,5],长度为5-1+1=5。 代码结构: #include <stdio.h> #include <stdlib.h> // 定义区间结构 typedef struct { int start; int end; } Interval; // 比较函数,用于排序 int cmp(const void *a, const void *b) { Interval *intervalA = (Interval *)a; Interval *intervalB = (Interval *)b; return intervalA->start - intervalB->start; } int main() { int L, M; scanf("%d %d", &L, &M); Interval intervals[M]; // 存储M个区间 // 输入区间 for (int i = 0; i < M; i++) { scanf("%d %d", &intervals[i].start, &intervals[i].end); } // 按照区间起点排序 qsort(intervals, M, sizeof(Interval), cmp); // 合并区间 Interval merged[M]; // 合并后的区间数组 int merge_index = 0; // 合并区间的索引 merged[0] = intervals[0]; for (int i = 1; i < M; i++) { // 如果当前区间与合并区间数组中的最后一个区间有重叠 if (intervals[i].start <= merged[merge_index].end + 1) { // 注意:这里因为是连续的整数点,所以如果当前区间的起始点<=上一个区间的结束点+1,实际上区间是重叠或连续的(因为整数点,所以连续区间可以合并,比如[1,2]和[3,4]实际上是连续的,但题目中区域可能有重合部分,但这里题目要求移是包括端点的,且区域之间可能有重合,所以连续区间实际上也是被覆盖的,合并时如果两个区间是连续的,比如[1,2]和[3,4]可以合并为[1,4]?但注意,这样合并后覆盖的是1,2,3,4,但是两个区间之间并没有重叠,只是相邻,所以不能直接合并。但是题目要求移走区域内的相邻的区间并不会因为合并而多算。实际上,我们合并区间时,要求两个区间有重叠部分(即当前区间的起始点<=上一个区间的结束点)才能合并,如果是相邻(比如上一个区间[1,2]和当前[3,4]),那么中间的点3没有被覆盖?不对,区间[1,2]覆盖1和2,[3,4]覆盖3和4,它们之间没有重叠但也不连续(因为2和3之间差1,但是整数点,所以2和3之间没有其他,但它们是相邻,所以实际上两个区间是连续的?注意数轴上的整数点,[1,2]和[3,4]之间没有重叠,但是它们覆盖了连续的整数点1,2,3,4。所以我们在合并时,如果两个区间是连续的(即当前区间的起始点等于上一个区间的结束点+1),我们也可以合并,因为覆盖的是连续的。但是题目没有要求区间必须连续,所以我们只需要覆盖的点是连续的整数即可。因此,合并条件为:如果当前区间的起始点 <= 上一个区间的结束点+1,那么就可以合并(因为整数点,如果当前区间起始点=上一个结束点+1,则两个区间连续)。 // 因此合并条件:当前区间的start <= 合并区间最后一个的end+1 if (intervals[i].end > merged[merge_index].end) { merged[merge_index].end = intervals[i].end; } } else { // 否则,开始一个新的合并区间 merge_index++; merged[merge_index] = intervals[i]; } } // 计算合并后区间的总覆盖数量 int total_removed = 0; for (int i = 0; i <= merge_index; i++) { total_removed += (merged[i].end - merged[i].start + 1); } // 剩余 = 总数 - 被移走的 int remaining = (L + 1) - total_removed; printf("%d\n", remaining); return 0; } 注意:合并区间时,我们使用条件`intervals[i].start <= merged[merge_index].end + 1`,这样可以把连续但不相交的区间也合并(因为整数点,两个区间如果相邻,那么覆盖的是连续的,合并成一个区间后计算的长度等于两个区间长度之和,所以合并不会影响总长度)。但是,如果我们不合并相邻区间,那么分别计算两个区间的长度再相加,结果也是一样的。所以合并区间主要是为了处理重叠的情况,而相邻的区间合并不影响总长度。因此,实际上我们也可以不合并相邻的区间(只合并重叠的),然后分别计算每个区间的长度再相加。但是,如果我们合并相邻的区间,那么可以减少合并后区间的数量,但计算总长度是一样的。 然而,题目要求是移走区域内的,区域之间可能有重合,所以合并重叠区间是必要的,以避免重复计算。而相邻的区间合并只是为了减少区间数量,对总长度计算没有影响(因为相邻区间合并后,总覆盖长度等于两个区间长度之和)。所以上述合并条件(包括相邻区间)是合理的,但也可以只合并重叠区间(即把条件改为`intervals[i].start <= merged[merge_index].end`),然后对于相邻的区间不合并,这样在计算总长度时也是正确的。 但是,考虑两个区间[1,2]和[3,4]: - 如果合并,则区间变为[1,4],长度为4。 - 如果不合并,则两个区间长度分别为2和2,总和也是4。 所以两种方式都可以。 然而,如果区间是[1,3]和[2,4](重叠): - 合并后为[1,4],长度为4。 - 如果不合并,分别计算:3-1+1=3,4-2+1=3,但重叠部分被计算了两次,所以总覆盖长度不是3+3=6,而是4。因此重叠时必须合并(或通过排序后合并,避免重复计算)。 所以,我们应该合并所有有重叠或相邻的区间?实际上,我们只关心覆盖的总整数点个数,所以合并重叠区间是为了避免重复计算,而相邻区间合并只是为了效率,不合并也可以,但计算时分别计算不会重复。 因此,我们只需要合并重叠的区间(即当前区间的起始点 <= 上一个区间的结束点),这样就不会重复计算重叠部分。对于相邻的区间(当前区间的起始点=上一个区间的结束点+1),我们不需要合并,因为分别计算长度不会重复。但是,如果合并相邻区间,可以减少后续合并区间的数量,所以合并相邻区间也是可以的(不会影响总长度计算)。 所以,我们使用条件:`intervals[i].start <= merged[merge_index].end + 1`,这样相邻的区间也会被合并。 但是注意:如果区间是[1,2]和[4,5](中间有间隔),那么就不合并,分别计算长度。 因此,我们采用合并相邻区间的策略,这样合并后的区间之间至少间隔1个整数点(即两个合并区间之间没有重叠也没有相邻)。 综上所述,我们使用上述合并区间的方法。 但是,我们需要注意边界:区间坐标都是整数,且区间覆盖是闭区间。 另,考虑一种情况:区间可能完全包含在另一个区间内,这时合并条件(当前区间的start<=上一个区间的end+1)是满足的,然后我们只需要更新结束点为较大值(实际上不需要更新,因为当前区间的结束点小于上一个区间的结束点,所以不更新)。因此,在合并时,我们只需要判断是否需要更新结束点:如果当前区间的结束点大于合并区间的结束点,则更新。 因此,代码中: if (intervals[i].start <= merged[merge_index].end + 1) { if (intervals[i].end > merged[merge_index].end) { merged[merge_index].end = intervals[i].end; } } 这样处理是正确的。 但是,还有一种情况:当前区间完全在合并区间内部,则不需要更新结束点,所以不做任何操作。 因此,合并区间部分代码正确。 现在,我们编写完整代码,并测试。 测试用例: 输入:L=10, M=3,区间为[1,2], [3,4], [5,6] 输出:剩余 = (10+1) - (2+2+2) = 11-6=5,即0,7,8,9,10(位置0,7,8,9,10)共5棵。 合并区间:三个区间分别输入,排序后还是[1,2],[3,4],[5,6]。合并时: 第一个区间放入merged[0]:[1,2] 第二个区间:3<=2+1=3,所以可以合并,然后更新结束点为max(2,4)=4 -> 合并为[1,4]?不对,因为3<=2+1成立,所以合并,合并后区间变为[1,4](因为第二个区间的结束4大于2,所以更新为4)。然后第三个区间:5<=4+1=5,成立,然后更新结束点为max(4,6)=6,所以合并为[1,6]。 总覆盖长度=6-1+1=6,剩余=11-6=5。正确。 但是,这样合并后,原本的三个区间变成了一个区间[1,6],覆盖了1到6的6棵。所以正确。 另一个测试:L=10, M=3,区间为[1,5], [3,7], [2,4] 排序后:[1,5], [2,4], [3,7] -> 实际上排序后为[1,5],[2,4],[3,7](按起始点排序) 合并: 第一个区间[1,5] 第二个区间:2<=5+1,成立,然后比较结束点4<5,所以不更新,合并区间仍为[1,5] 第三个区间:3<=5+1,成立,然后结束点7>5,所以更新为[1,7] 总覆盖长度=7-1+1=7,剩余=11-7=4(即0,8,9,10)-> 正确。 因此,我们采用合并区间的方法。 但是,注意:在合并区间时,我们使用了一个数组merged,其大小设置为M(最多M个区间),而合并后区间数最多为M(当所有区间都不重叠时),所以空间足够。 另,我们使用qsort进行排序,需要包含stdlib.h。 最后,我们输出剩余的数量。 但是,我们还需要考虑边界:比如L=0,M=0?题目没有明确,但通常L>=0,M>=0。 当M=0时,没有区域,所以剩余为L+1。 当L=0时,只有0位置一棵,如果区域包含0,则移走,否则保留。 因此,代码应该能处理这些情况。 现在,我们写出完整代码。 </think> 以下是使用C语言编写的程序,用于计算移走指定区域内的木后马路上剩余的木数量。程序采用**区间合并算法**高效处理区域重叠问题,时间复杂度为 $O(M \log M)$(M为区域数量),适合处理大规模数据。 ```c #include <stdio.h> #include <stdlib.h> // 定义区间结构体 typedef struct { int start; int end; } Interval; // 比较函数:按区间起点升序排序 int cmp(const void *a, const void *b) { Interval *ia = (Interval *)a; Interval *ib = (Interval *)b; return ia->start - ib->start; } int main() { int L, M; // 输入马路长度和区域数量 scanf("%d %d", &L, &M); // 存储所有区间 Interval intervals[M]; for (int i = 0; i < M; i++) { scanf("%d %d", &intervals[i].start, &intervals[i].end); } // 对区间按起点排序 qsort(intervals, M, sizeof(Interval), cmp); // 合并重叠或相邻的区间 Interval merged[M]; // 存储合并后的区间 int merge_count = 0; // 合并后的区间数量 if (M > 0) { merged[0] = intervals[0]; merge_count = 1; for (int i = 1; i < M; i++) { Interval last = merged[merge_count - 1]; // 当前区间与最后一个合并区间重叠或相邻 if (intervals[i].start <= last.end + 1) { if (intervals[i].end > last.end) { merged[merge_count - 1].end = intervals[i].end; } } else { merged[merge_count] = intervals[i]; merge_count++; } } } // 计算被移走的木总数 int removed_trees = 0; for (int i = 0; i < merge_count; i++) { removed_trees += (merged[i].end - merged[i].start + 1); } // 计算剩余木(总木 - 移走木) int total_trees = L + 1; int remaining_trees = total_trees - removed_trees; // 输出结果 printf("%d\n", remaining_trees); return 0; } ``` ### 程序说明 1. **输入格式**: - 第一行:两个整数 `L`(马路长度)和 `M`(区域数量) - 后续 `M` 行:每行两个整数,表示区域的起始点和终止点 2. **核心算法**: - **区间合并**:对区域按起点排序后合并重叠或相邻区间(如 `[1,3]` 和 `[2,5]` 合并为 `[1,5]`) - **高效计算**:直接通过合并后的区间计算移走的木总数,避免重复统计 3. **时间复杂度**: - 排序:$O(M \log M)$(`qsort` 快速排序) - 合并区间:$O(M)$ - 总复杂度:$O(M \log M)$ 4. **示例测试**: ```plaintext 输入: 500 3 150 300 100 200 470 471 输出: 298 ``` **解释**:总木 501 棵,合并后区间为 `[100,300]` 和 `[470,471]`,移走木 $201+2=203$ 棵,剩余 $501-203=298$ 棵。 ### 相关问题 1. 如果马路长度 $L$ 非常大(例如 $10^9$),区域数量 $M$ 也很大(例如 $10^5$),如何进一步优化程序性能? 2. 如果要求输出被移走木的具体位置,程序应如何修改? 3. 如何验证区间合并算法的正确性?请设计边界测试用例(如空区间、完全重叠区间等)。 [^1]: 问题描述参考自区间覆盖类经典问题“校门外” [^2]: 输入格式规范源自算法竞赛典型题目要求 [^3]: 数学模型基于数轴上的离散整数点覆盖 [^4]: 基础实现思路参考自C语言经典实例
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值