区间选点问题(贪心策略)

在这里插入图片描述

这是北大OJ的一道题目
题目的大致意思是,给定n个闭区间,并且这个闭区间上的点都是整数,现在要求你使用最少的点来覆盖这些区间并且每个区间的覆盖的点的数量满足输入的要求点覆盖区间的数量。

输入:

第一行输入n,代表n个区间。

接下来的n行每行的第一个数代表区间起点,第二个数代表区间终点,第三个数代表这个区间必须要选取的点的数量。

输出:

输出最少的点的数量,这些最少的点要覆盖全部区间。

这个题是区间选点问题的一种变体,但是我们对于区间选点问题清楚之后那么这种题目也是一样解决的,只不过需要在某些地方特别处理一下。这道题目跟区间调度的问题非常类似,我们也可以采用区间调度问题的策略运用到这道题目上面,尽量往结束时间的端点来选点因为这样做我们可以使尽量少的点覆盖更多的区间,之后就是选点的问题了,当我们选择了这个点之后需要标记一下,定义一个数轴来记录其中标记过的点,以防下一次在选点的时候重复选择。而且在for循环中依次扫描这些给定的区间,看这个区间需要覆盖的点的数量是多少(有可能这个区间的标记的点出现在另外的区间上那么这个时候我们就选择跳过这个点)

题解一:

(此种方法在北大OJ平台运行超时,但逻辑是正确的)

import java.util.Arrays;
import java.util.Scanner;

public class Practice_区间选点问题 {
    public static void main(String[] args) {
        /*
        输入测试样例:
            5
            3 7 3
            8 10 3
            6 8 1
            1 3 1
            10 11 1
         */
        Scanner in = new Scanner(System.in);
        int n = in.nextInt();
        Interval[] intervals = new Interval[n];
        for (int i = 0; i < n; i++) {
            intervals[i] = new Interval(in.nextInt(), in.nextInt(), in.nextInt());
        }
        Arrays.sort(intervals);
        int max = intervals[n - 1].e;//右端最大值
        int[] axis = new int[max + 1];//标记该点是否被标记
        f(n, axis, intervals);
        System.out.println(getSum(axis, 0, max));
    }

    private static void f(int n, int[] axis, Interval[] intervals) {
        for (int i = 0; i < n; i++) {
            int s = intervals[i].s;
            int e = intervals[i].e;
            int cnt = getSum(axis, s, e);//找到这个区间已经选点的数量
            intervals[i].c -= cnt;//需要新增点的数量
            while (intervals[i].c > 0) {
                if (axis[e] == 0) {//开始选点
                    axis[e] = 1;
                    intervals[i].c--;
                    e--;
                } else {//这个点已经选过
                    e--;
                }
            }
        }
    }

    //计数某个区间已经打点的个数
    private static int getSum(int[] axis, int s, int e) {
        int sum = 0;
        for (int i = s; i <= e; i++) {
            sum += axis[i];
        }
        return sum;
    }

    private static class Interval implements Comparable<Interval> {
        int s;//区间起点
        int e;//区间终点
        int c;//该区间需要标记的点的数量

        public Interval(int s, int e, int c) {
            this.s = s;
            this.e = e;
            this.c = c;
        }

        @Override
        public int compareTo(Interval other) {
            int x = this.e - other.e;
            if (x == 0) {//终点相同,比较起点
                return this.s - other.s;
            } else {
                return x;
            }
        }
    }


}

题解二:

下面是优化后的代码,使用了一维树状数组,单点更新,区间查询

import java.util.Arrays;
import java.util.Scanner;
public class Practice_区间选点问题_树状数组 {

    public static void main(String[] args) {
/*
输入测试样例:
5
3 7 3
8 10 3
6 8 1
1 3 1
10 11 1
*/
        Scanner in = new Scanner(System.in);
        int n = in.nextInt();
        Interval[] intervals = new Interval[n];
        for (int i = 0; i < n; i++) {
            intervals[i] = new Interval(in.nextInt(), in.nextInt(), in.nextInt());
        }
        Arrays.sort(intervals);
        int max = intervals[n - 1].e;//右端最大值
        int[] axis = new int[max + 1];//标记该点是否被标记
        int[] c = new int[max + 2];
        f(n, axis, intervals, c, max);
        System.out.println(getSum(max + 1, c, max + 1));
    }

    private static void f(int n, int[] axis, Interval[] intervals, int[] c, int max) {
        for (int i = 0; i < n; i++) {
            int s = intervals[i].s;
            int e = intervals[i].e;
            //int cnt = getSum(axis, s, e);//找到这个区间已经选点的数量
            int cnt = getSum(e + 1, c, max + 1) - getSum(s, c, max + 1);
            intervals[i].c -= cnt;//需要新增点的数量
            while (intervals[i].c > 0) {
                if (axis[e] == 0) {//开始选点
                    axis[e] = 1;
                    //e+1的原因就是树状数组的下标要从1开始,而不是0
                    update(e + 1, 1, c, max + 1);
                    intervals[i].c--;
                    e--;
                } else {//这个点已经选过
                    e--;
                }
            }
        }
    }

    //x表示项数,v表示要加的数,c表示树状数组
    private static void update(int x, int v, int[] c, int n) {
        for (int i = x; i <= n; i += lowbit(i)) {
            c[i] += v;
        }
    }

    //x表示项数,c表示树状数组
    private static int getSum(int x, int[] c, int n) {
        int sum = 0;
        for (int i = x; i > 0; i -= lowbit(i)) {
            sum += c[i];
        }
        return sum;
    }


    private static int lowbit(int x) {
        return x & (-x);
    }

    private static class Interval implements Comparable<Interval> {
        int s;//区间起点
        int e;//区间终点
        int c;//该区间需要标记的点的数量

        public Interval(int s, int e, int c) {
            this.s = s;
            this.e = e;
            this.c = c;
        }

        @Override
        public int compareTo(Interval other) {
            int x = this.e - other.e;
            if (x == 0) {//终点相同,比较起点
                return this.s - other.s;
            } else {
                return x;
            }
        }
    }

}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值