挑战程序竞赛系列(7):2.1一往直前!贪心法

详细代码可以fork下Github上leetcode项目,不定期更新。

练习题如下:
1. POJ 2376: Cleaning Shifts
2. POJ 1328: Radar Installation
3. POJ 3190: Stall Reservations

POJ 2376: Cleaning Shifts

思路:
贪心,先按开始时间排序,如刚开始begin = 1时,从大于begin的所有牛中选择end最大的,为下一轮的begin时间,这样一直继续下去,直到大于T即可。代码如下:

public class SolutionDay23_P2376 {

    private static class Pair{
        int s; 
        int e;
        Pair(int s, int e){
            this.s = s;
            this.e = e;
        }

        @Override
        public String toString() {
            return "["+s+","+e+"]";
        };
    }

    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        int N = in.nextInt();
        int T = in.nextInt();
        Pair[] pairs = new Pair[N];
        for (int i = 0; i < N; i++){
            int s = in.nextInt();
            int e = in.nextInt();
            pairs[i] = new Pair(s,e);
        }
        System.out.println(solve(pairs,T));
        in.close();
    }

    private static int solve(Pair[] pairs, int T){
        Arrays.sort(pairs, new Comparator<Pair>() {
            @Override
            public int compare(Pair o1, Pair o2) {
                return o1.s != o2.s ? o1.s - o2.s : o1.e - o2.e ;
            }
        });

        int pos = 0;
        int end = 0;
        int step = 0;
        int begin = 0;
        while (end < T){
            begin = end + 1;
            for (int i = pos; i < pairs.length; i++){
                if (pairs[i].s <= begin){
                    end = Math.max(end, pairs[i].e);
                }else{
                    pos = i;
                    break;
                }
            }

            if (begin > end){
                return -1;
            }
            else{
                step ++;
            }
        }
        return step;
    }

}

POJ 1328: Radar Installation

思路:
刚开始的想法是根据岛屿的y坐标进行排序,然后每次选择最大的y值,以它的x为雷达中心,把所有覆盖的岛屿删除,再选择第二个雷达,依此类推,直到没有岛屿,WA了。其实想想也挺蠢的,你就那么确定雷达的中心一定是在某个岛屿的下方?确定雷达的横坐标显然不是一个有效的做法。(因为它可以是个范围)

所以,雷达既然是范围,我们就不可能从雷达的角度去思考该问题,反过来,我们可以根据岛屿求得雷达可在的区域列表,这样计算过后,只要尽可能的让这些区域列表重合即可。

根据岛屿所在的位置,计算与x轴相交的两个点,记为start和end,计算公式如下:

以岛屿为圆心,构造:
(x-x1)^2 + (y-y1)^2 = d^2;
令 y = 0;
所以有:
start = x - (d*d - y*y)
end = x + (d*d - y*y)

好了,知道了每个雷达可在的区域,我们就开始选择吧。目标:尽可能的让所有区域列表重合,所以我们从最左端的start开始,它会有一个end区间。现在遍历start小于end区间的所有区域,最小end区间,一旦某个start超过end,就增加一个雷达。代码如下:

public class SolutionDay24_P1328 {

    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        int id = 0;
        while (true){
            String[] info = in.nextLine().trim().split(" ");
            int n = Integer.parseInt(info[0]);
            int d = Integer.parseInt(info[1]);
            if (n == 0 && d == 0) break;
            int[][] islands = new int[n][2];
            for (int i = 0; i < n; i++){
                String[] pos = in.nextLine().trim().split(" ");
                islands[i][0] = Integer.parseInt(pos[0]);
                islands[i][1] = Integer.parseInt(pos[1]);
            }
            in.nextLine();
            System.out.println("Case "+(++id)+": "+solve(islands, d));
        }
        in.close();
    }

    private static class Section{
        double start;
        double end;
        Section(double start, double end){
            this.start = start;
            this.end = end;
        }
    }
    private static int solve(int[][] islands, int d){
        Section[] sections = new Section[islands.length];
        for (int i = 0; i < islands.length; i++){
            int x = islands[i][0];
            int y = islands[i][1];
            if (y > d) return -1;
            double sqrt = Math.sqrt(d*d-y*y);
            sections[i] = new Section(x-sqrt, x+sqrt);
        }
        Arrays.sort(sections, new Comparator<Section>() {
            @Override
            public int compare(Section o1, Section o2) {
            //强转会出错,注意这个地方
                return (o1.start != o2.start ? o1.start - o2.start : o1.end - o2.end) > 0 ? 1 : -1;
            }
        });

        double minEnd = -1 << 30;
        int step = 0;
        for (int i = 0; i < sections.length; i++){
            if(sections[i].start <= minEnd){
                minEnd = Math.min(minEnd, sections[i].end);
            }else{
                step ++;
                minEnd = sections[i].end;
            }
        }
        return step;
    }
}

POJ 3190: Stall Reservations

不得不吐槽下,POJ系统对JAVA的支持太弱了,优先队列的接口居然能编译错误,更别说支持Java 8的一些特性了。

还是一道区间如何选择的问题,贪心策略:
总是先安排A小的那头奶牛,所以先对A进行排序,接着就是尽可能的把每头奶牛放入stall中,最糟糕的情况就是所有奶牛的喝奶区间都重合,这必然需要安排大小为奶牛数的stall来满足它们。

所以,如果区间不重,那么我们就可以把奶牛放在一个stall中,但这应该怎么操作呢?

我的想法:
针对第一头奶牛,能够得到一个end区间,此时,遍历余下的n-1头奶牛,把start > end的奶牛给筛选出来,然后更新end区间,并且删除该奶牛,直至末尾。

若还有剩余的奶牛,则再建个stall,重复上述步骤,直到所有奶牛被安置完成。

这是最基本的求解思路,但缺点就是需要遍历O(k∗n)次,k为stall数,n为奶牛数。

在上述想法中,我们还少使用了一个性质,每次安排一头新牛进入stall时,一定选择stall中end最小的那个,因为end较大的更不可能给这头新牛,必然区间重合。所以每次,只需要判断peek的stall是否满足cow.start > stall.end,如果满足则加入stall,不满足则创建新的stall来放这头牛。(贪心在这)

代码如下:

public class SolutionDay23_P3190 {
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        int n = in.nextInt();
        int[][] cows = new int[n][2];
        for (int i = 0; i < n; i++){
            cows[i][0] = in.nextInt();
            cows[i][1] = in.nextInt();
        }
        solve(cows, n);
        in.close();
    }

    private static class MilkTime{
        int start;
        int end;
        int id;
        public MilkTime(int start, int end,int id) {
            this.start = start;
            this.end = end;
            this.id = id;
        }
    }

    private static class Stall{
        int end;
        int id;
        public Stall(int end, int id){
            this.end = end;
            this.id = id;
        }

        @Override
        public String toString() {
            return "[end: "+end+" id: "+id+"]";
        }
    }

    private static void solve(int[][] cows, int n){
        MilkTime[] milks = new MilkTime[n];
        for (int i = 0; i < n; i++){
            milks[i] = new MilkTime(cows[i][0], cows[i][1], i);
        }

        Arrays.sort(milks,new Comparator<MilkTime>() {
            @Override
            public int compare(MilkTime o1, MilkTime o2) {
                return o1.start -  o2.start;
            }
        });

        PriorityQueue<Stall> queue = new PriorityQueue<>(new Comparator<Stall>() {
            @Override
            public int compare(Stall o1, Stall o2) {
                return o1.end - o2.end;
            }
        });

        int[] result = new int[n];
        for (int i = 0; i < n; i++){
            if (i == 0){
                put(queue, milks[i], true, result);
                continue;
            }
            if (milks[i].start <= queue.peek().end){
                put(queue, milks[i], true, result);
            }else{
                put(queue, milks[i], false, result);
            }
        }
        System.out.println(queue.size());
        for (int i = 0; i < n; i++){
            System.out.println(result[i]);
        }
    }

    private static void put(PriorityQueue<Stall> queue, MilkTime cow, boolean isNew, int[] result){
        if(isNew){
            int id = queue.size() + 1;
            result[cow.id] = id;
            Stall stall = new Stall(cow.end, id);
            queue.offer(stall);
        }else{
            Stall stall = queue.poll();
            stall.end = cow.end;
            result[cow.id] = stall.id;
            queue.offer(stall);
        }
    }
}