poj-1065

//5340K    313MS    Java
import java.util.Collections;
import java.util.List;
import java.util.ArrayList;
import java.util.Scanner;
import java.util.Comparator;
import java.util.Arrays;

public class Main {
    private static final int MAX = 5000;
    private class Stick implements Comparable{
        public int mLength;
        public int mWeight;
        public Stick(int length, int weight) {
            mLength = length;
            mWeight = weight;
        }
        public int compareTo(Object o) {
            // int lenthCmpRes = this.mLength - ((Stick)o).mLength;
            int lenthCmpRes = this.mLength - ((Stick)o).mLength;
            return lenthCmpRes != 0 ? lenthCmpRes :
                        this.mWeight - ((Stick)o).mWeight;
        }
    }

    private class StickComparator implements Comparator<Stick> {
        @Override
            public int compare(Stick o1, Stick o2) {
                int lengthCmpRes = o1.mLength - o2.mLength;
                return lengthCmpRes != 0 ? lengthCmpRes:
                        o1.mWeight - o2.mWeight;
        }
    }

    private StickComparator mComparator;
    private Stick[] Sticks;
    private byte[] flag;
    private int mCurStickInsertPos;

    public void addStick(int length, int weight) {
        Sticks[mCurStickInsertPos++] = new Stick(length, weight);
    }

    public void getMinSetupTime() {
        Arrays.sort(Sticks, 0, mCurStickInsertPos, mComparator);
        int setupTime = 0;

        for (int i = 0; i < mCurStickInsertPos; i++) {
            if (flag[i] == 0) { // if not handled yet
                // System.out.println(i);
                setupTime++;
                int curWeight = Sticks[i].mWeight;
                flag[i] = 1;
                for (int j = i+1; j < mCurStickInsertPos ;j++) {
                    if (curWeight <= Sticks[j].mWeight && flag[j] == 0) {
                        curWeight = Sticks[j].mWeight;
                        flag[j] = 1;
                        // System.out.println(Sticks[j].mLength + " " + Sticks[j].mWeight);
                    }
                }
            }
        }
        System.out.println(setupTime);
    }

    public Main() {
        mCurStickInsertPos = 0;
        flag = new byte[MAX];
        Sticks = new Stick[MAX];
        mComparator = new StickComparator();
    }

    public void reset() {
        for (int i = 0; i < MAX; i++) {
            flag[i] = 0;
        }
        mCurStickInsertPos = 0;
    }

    public static void main(String[] args) {
        Main solver = new Main();
        Scanner input = new Scanner(System.in);
        int caseNum = input.nextInt();
        for (int i = 0; i < caseNum; i++) {
            solver.reset();
            int stickNum = input.nextInt();
            for (int j = 0; j < stickNum; j++) {
                int length = input.nextInt();
                int weight = input.nextInt();
                solver.addStick(length, weight);
            }
            solver.getMinSetupTime();
        }
    }

}

//5340K    313MS    Java

贪心水题,不过却犯了不少乌龙。。。

首先是把题目的大小比较给看错了,原题是后面的stick的l和w都比前一个长才不用花时间setup,我给看反了,而两个例子在这种情况下答案也一样,看不出错...

然后是理解偏差,一个l1w2的stick在接着处理了一个4l5w的stick以后,l和w要按照最近处理的stick来参考,比如这时候,3l4w的stick就需要setup时间了. 一开始

给理解成只要能接着处理,l和w都按第一个stick的来取。

使用java本身也出了不少语言错误,java还是要多练练,POJ还要求class必须是"Main"

除去这些,题本身求解倒不难,

为了熟悉java的数组排序,这里没有用最快的方法,不过这道题对时间也不敏感。

思路是这样的,一开始按照l或者w来进行升序排序,得到一个有序的stick数组S(这里假设按照L升序排序)。

还要维护一个stick的flag数组,标示此stick是否被处理过了。

然后从S的第一个成员A开始,setupTime要+1, 取得该成员的L和W,那么对L来说,A后面所有stick的L都>=L(升序),因此L不用管了,

而W因为没有经过排序,因此要向后遍历,只要找到>=W的stick,就是可以不需要setupt时间的stick,同时在flag中标示此stick已经被处理了。

同时W要更新为这个stick的W,一直向后,直到遍历完S。

至此以A为开始的不需要额外setuptime的序列就完了,接下来,要找下一个还没有被处理的stick,作为新一轮没有额外setuptime序列的起始stick,

然后重复上面的过程,直到所有的stick都被处理完,

至此得到的setuptime就是最小的。

更效率的办法是每个L都维护一个自己的W升序序列(其实就是排序时,L相等的情况下,再考虑W),这样在对于某个L的stick序列,查找对于某个W能够不需要额外setup时间的stick就可以很快得到。

其实我不怎么喜欢贪心法,对于一些直观的题还好,但是对于某些题,

如何得到贪心的解以及证明贪心是最好的,这个过程其实没有一个统一的流程(和DP不一样),更偏向于一种直觉和智力上的挑战,

对于我这样的普通人来说,是很难学来的.

至于本题的贪心证明,先贴一个别人的证明:

其实这题需要两次贪心


初始思路,
最长不降子序列或者floyd求最长路,floyd肯定超时,写nlogn的最长不降子序列也非常蛋疼


于是,
第一次贪心:其实并非每次都要求一个最长的序列,然后删除,只要选出的序列符合"极大"即可。这里的极大和函数的极值是一个概念。
例如,(1,4) (4,4) (5,3) (9,4) 其中最大(最长)的一个序列为 (1,4)->(4,4)->(9,4)
极大的序列 为 (5,3)->(9,4)
证明:很简单,因为所有的木头都需要被加工,并且与加工的先后顺序无关,那么该木头一定
需要在某个序列中,而我们每次选择的序列是极大的,故每次操作都没有遗漏可加入的木头,因此总的操作数是最少的。


第二次贪心:得到一个极大的序列,直接枚举的复杂度是O(n^3),显然超时。进一步思考


考虑任意的序列:对于任意的序列(a,b)->(c,d) 如果该序列是合法的,那么必然满足
a<=c 且 b<=d ,不等式相加可得 a+c<=b+d


上面的不等式提示我们,对于任意的合法序列,必然满足长度与重量之和 是不降的


因此,如果我们将长度作为第一关键字,重量做为第二关键字进行升序排序,那么从左到右,选取所有w不降的序列,则该序列必然是极大序列。


证明:1.序列是合法的,这个正确性显然。2.序列是极大的,因为我们在1的前提下,尽可能多的选取木头


于是我们得到了最终的算法:O(nlgn+n^2)


1.先按第一关键字排序,若第一关键字相等,则按第二关键字排序
2.找出w不降,且未被标记的所有木头,将之标记
3.直到所有的木棍都被标记,否则继续执行2


自己的理解:

从直觉上想,如果我们不按照L或者W的升序顺序来进行操作,比如 不从L1(最小的L的stick群)开始,而是从L2(第2小L的stick群)开始,那样,后面不管怎么做,L1的stick群都不可能在本次没有额外的setup的前提下被处理(因为L永远>= L2 ),必然会比从L1开始额外的增加一次setup(从L2开始绝不对比从L1开始需要额外的setup time),这样基本证明了从L1开始是最好的,

然后是在从某个LN集群开始处理,在L确定情况下,当然是能够在不增加setup time的前提下处理越多的stick越好,这个证明其实是显而易见的。

这个证明还是不够规范,不是严格的数学推理,贪心相对于DP的优势就在于从上到下,效率高。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值