小朋友学TopCoder(6):SRM144 DIV1 1100-point

Problem Statement

NOTE: There are images in the examples section of this problem statement that help describe the problem. Please view the problem statement in the HTML window to view them.

Given a picture composed entirely of horizontal and vertical line segments, calculate the minimum number of times you must lift your pen to trace every line segment in the picture exactly n times.

Each line segment will be of the form "<x1> <y1> <x2> <y2>" (quotes for clarity), representing a segment from (x1,y1) to (x2,y2). Segments may cross each other. Segments may also overlap, in which case you should count the overlapping region as appearing in the drawing only once. For example, say the drawing were composed of two lines: one from (6,4) to (9,4), and one from (8,4) to (14,4). Even though they overlap from (8,4) to (9,4), you should treat the drawing as if it were a single line from (6,4) to (14,4). You would not need to lift your pen at all to trace this drawing.

Definition

Class:PenLift
Method:numTimes
Parameters:String[], int
Returns:int
Method signature:int numTimes(String[] segments, int n)
(be sure your method is public)

Limits

Time limit (s):2.000
Memory limit (MB):64

Notes

-The pen starts on the paper at a location of your choice. This initial placement does not count toward the number of times that the pen needs to be lifted.

Constraints

-segments will contain between 1 and 50 elements, inclusive.
-Each element of segments will contain between 7 and 50 characters, inclusive.
-Each element of segments will be in the format "<X1><Y1><X2>_<Y2>" (quotes for clarity). The underscore character represents exactly one space. The string will have no leading or trailing spaces.
-<X1>, <Y1>, <X2>, and <Y2> will each be between -1000000 and 1000000, inclusive, with no unnecessary leading zeroes.
-Each element of segments will represent a horizontal or vertical line segment. No line segment will reduce to a point.
-n will be between 1 and 1000000, inclusive.

Examples

(0)
{"-10 0 10 0","0 -10 0 10"}
1
Returns: 1

This picture looks like a plus sign centered at the origin. One way to trace this image is to start your pen at (-10,0), move right to (10,0), lift your pen and place it at (0,-10), and then move up to (0,10). There is no way to trace the picture without lifting your pen at all, so the method returns 1.

(1)
{"-10 0 0 0","0 0 10 0","0 -10 0 0","0 0 0 10"}
1
Returns: 1

The picture is the same as the previous example, except that it has been described with four line segments instead of two. Therefore, the method still returns 1.

(2)
{"-10 0 0 0","0 0 10 0","0 -10 0 0","0 0 0 10"}
4
Returns: 0

You are now required to trace each segment exactly 4 times. You can do so without lifting your pen at all. Start at (0,0). Move your pen left to (-10,0), then back right to (0,0), then left again to (-10,0), then right again to (0,0). You have now traced the first line segment 4 times. Repeat this process for the other three segments as well. Since no pen lifts were required, the method returns 0.

(3)
{"0 0 1 0", "2 0 4 0", "5 0 8 0", "9 0 13 0",
"0 1 1 1", "2 1 4 1", "5 1 8 1", "9 1 13 1",
"0 0 0 1", "1 0 1 1", "2 0 2 1", "3 0 3 1",
"4 0 4 1", "5 0 5 1", "6 0 6 1", "7 0 7 1",
"8 0 8 1", "9 0 9 1", "10 0 10 1", "11 0 11 1",
"12 0 12 1", "13 0 13 1"}
1
Returns: 6

The picture looks like this:

penlift1.gif

To trace the picture using the minimum number of pen lifts, refer to the following diagram:

penlift2.gif

Start by placing your pen at the yellow dot. Trace the yellow square. Now lift your pen and place it on the red dot. Move downward, tracing the vertical line segment, and then around the perimeter of the red rectangle. Lift your pen again and place it on the green dot. Trace the green lines using the same method as you did for the red lines. Lift your pen a third time, placing it on the magenta dot. Trace the magenta lines in a similar fashion. You will need to lift your pen three more times to trace each of the leftover white segments, for a grand total of 6 pen lifts.

(4)
{"-2 6 -2 1", "2 6 2 1", "6 -2 1 -2", "6 2 1 2",
"-2 5 -2 -1", "2 5 2 -1", "5 -2 -1 -2", "5 2 -1 2",
"-2 1 -2 -5", "2 1 2 -5", "1 -2 -5 -2", "1 2 -5 2",
"-2 -1 -2 -6","2 -1 2 -6","-1 -2 -6 -2","-1 2 -6 2"}
5
Returns: 3

This is an example of overlap. Once all the segments are drawn, the picture looks like this:

penlift3.gif

You would need to lift your pen 3 times to trace every segment in this drawing exactly 5 times.

(5)
{"-252927 -1000000 -252927 549481","628981 580961 -971965 580961",
"159038 -171934 159038 -420875","159038 923907 159038 418077",
"1000000 1000000 -909294 1000000","1000000 -420875 1000000 66849",
"1000000 -171934 628981 -171934","411096 66849 411096 -420875",
"-1000000 -420875 -396104 -420875","1000000 1000000 159038 1000000",
"411096 66849 411096 521448","-971965 580961 -909294 580961",
"159038 66849 159038 -1000000","-971965 1000000 725240 1000000",
"-396104 -420875 -396104 -171934","-909294 521448 628981 521448",
"-909294 1000000 -909294 -1000000","628981 1000000 -909294 1000000",
"628981 418077 -396104 418077","-971965 -420875 159038 -420875",
"1000000 -1000000 -396104 -1000000","-971965 66849 159038 66849",
"-909294 418077 1000000 418077","-909294 418077 411096 418077",
"725240 521448 725240 418077","-252927 -1000000 -1000000 -1000000",
"411096 549481 -1000000 549481","628981 -171934 628981 923907",
"-1000000 66849 -1000000 521448","-396104 66849 -396104 1000000",
"628981 -1000000 628981 521448","-971965 521448 -396104 521448",
"-1000000 418077 1000000 418077","-1000000 521448 -252927 521448",
"725240 -420875 725240 -1000000","-1000000 549481 -1000000 -420875",
"159038 521448 -396104 521448","-1000000 521448 -252927 521448",
"628981 580961 628981 549481","628981 -1000000 628981 521448",
"1000000 66849 1000000 -171934","-396104 66849 159038 66849",
"1000000 66849 -396104 66849","628981 1000000 628981 521448",
"-252927 923907 -252927 580961","1000000 549481 -971965 549481",
"-909294 66849 628981 66849","-252927 418077 628981 418077",
"159038 -171934 -909294 -171934","-252927 549481 159038 549481"}
824759
Returns: 19

分析

本题实际上就是一笔画问题,可参考 http://www.jianshu.com/p/07771307c0bf

代码实现

import java.util.ArrayList;
import java.util.Collections;

public class PenLift {  
   
    // 测试方法
    public static void main(String[] args) {
        int num;
        
        // // example 0
        // String[] segs = {"-10 0 10 0", "0 -10 0 10"};
        // num = 1;
        
        // // example 1,2 
        // String[] segs = {"-10 0 0 0", "0 0 10 0", "0 -10 0 0", "0 0 0 10"};
        // num = 1;
        // num = 4;
        
        // // example 3
        // String[] segs = {"0 0 1 0",   "2 0 4 0",   "5 0 8 0",   "9 0 13 0",
                        // "0 1 1 1",   "2 1 4 1",   "5 1 8 1",   "9 1 13 1",
                        // "0 0 0 1",   "1 0 1 1",   "2 0 2 1",   "3 0 3 1",
                        // "4 0 4 1",   "5 0 5 1",   "6 0 6 1",   "7 0 7 1",
                        // "8 0 8 1",   "9 0 9 1",   "10 0 10 1", "11 0 11 1",
                        // "12 0 12 1", "13 0 13 1"};
        // num = 1;
        
        // example 4
        String[] segs = {"-2 6 -2 1",  "2 6 2 1",  "6 -2 1 -2",  "6 2 1 2",
                        "-2 5 -2 -1", "2 5 2 -1", "5 -2 -1 -2", "5 2 -1 2",
                        "-2 1 -2 -5", "2 1 2 -5", "1 -2 -5 -2", "1 2 -5 2",
                        "-2 -1 -2 -6","2 -1 2 -6","-1 -2 -6 -2","-1 2 -6 2"};
        num = 5;
        
        System.out.println(numTimes(segs, num));
    }
    
    static ArrayList<Point> points;

    public static int numTimes(String[] segments, int n) {
        ArrayList<Segment> segs = new ArrayList<Segment>();
        for (int i = 0; i < segments.length; i++) {
            String str[] = segments[i].split(" ");
            int x1 = Integer.parseInt(str[0]);
            int y1 = Integer.parseInt(str[1]);
            int x2 = Integer.parseInt(str[2]);
            int y2 = Integer.parseInt(str[3]);
            segs.add(new Segment(x1, y1, x2, y2));
        }
        
        for (int i = 0; i < segs.size(); i++) {
            Segment seg1 = segs.get(i);
            for (int j = i + 1; j<segs.size(); j++) {
                Segment seg2 = segs.get(j);
                if (Segment.isOverlap(seg1, seg2) == true) {
                    // 将两条重合的线段,合并成一条
                    segs.set(i, Segment.combine(seg1, seg2));
                    segs.remove(j);
                    i--;
                    break;
                }
            }
        }

        points = new ArrayList<Point>();
        // 把所有线段的端点、交点添加到points中
        for (int i = 0; i < segs.size(); i++) {
            Segment seg = segs.get(i);
            Point p1 = new Point(seg.x1, seg.y1);
            if (points.indexOf(p1) == -1) {
                points.add(p1);
            }
            
            Point p2 = new Point(seg.x2, seg.y2);
            if (points.indexOf(p2) == -1) {
                points.add(p2);
            }
            
            for (int j = 0; j < i; j++) {
                Segment seg1 = segs.get(j);
                Point cross = Segment.intersect(seg, seg1);
                if (cross != null) {
                    if (points.indexOf(cross) == -1) {
                        points.add(cross);
                    }
                }
            }
        }
        
        // 把points中的所有结点,根据连通关系放到不同的line中
        for (int i = 0; i < segs.size(); i++) {
            Segment seg = segs.get(i);
            ArrayList<Point> line = new ArrayList<Point>();
            for (int j = 0; j < points.size(); j++) {
                // 同一条线上有哪些点
                Point point = points.get(j);
                if (point.x >= seg.x1 && point.x <= seg.x2 && point.y >= seg.y1 && point.y <= seg.y2) {
                    line.add(point);
                }
            }
            
            Collections.sort(line);
            
            for (int j = 0; j < line.size() - 1; j++) {
                line.get(j).adjacents.add(points.indexOf(line.get(j + 1)));
                ArrayList<Integer> ids = line.get(j).adjacents;
                line.get(j + 1).adjacents.add(points.indexOf(line.get(j)));
                ArrayList<Integer> ids2 = line.get(j+1).adjacents;
            }
        }
        
        int count = 0;
        // 求奇点个数
        for (int i = 0; i < points.size(); i++) {
            if (points.get(i).visited == false) {
                points.get(i).visited = true;
                int odd = DFS(i);
                // 若n为偶数,points.get(i)必然不是奇点,可以一笔画
                if (n % 2 == 0 || odd == 0) {
                    count++;
                } else {
                    count += odd / 2;
                }
            }
        }
        
        return count - 1;
    }

    // 深度优先搜索
    private static int DFS(int index) {
        Point point = points.get(index);
        int odd;
        if (point.adjacents.size() % 2 == 0) {
            odd = 0;
        } else {
            odd = 1;
        }
        
        // 累加points.get(index)所有相邻点的奇点数量
        for (int i = 0; i < point.adjacents.size(); i++) {
            int next = point.adjacents.get(i);
            if (points.get(next).visited == false) {
                points.get(next).visited = true;
                odd += DFS(next);
            }
        }
        
        return odd;
    }

    
    // 内部类:线段
    static class Segment {
        int x1;
        int y1;
        int x2;
        int y2;
        boolean horizontal;

        public Segment(int X1, int Y1, int X2, int Y2) {
            this.x1 = Math.min(X1, X2);
            this.y1 = Math.min(Y1, Y2);
            this.x2 = Math.max(X1, X2);
            this.y2 = Math.max(Y1, Y2);
            
            if (this.y1 == this.y2) {
                this.horizontal = true;
            } else {
                this.horizontal = false;
            }
        }
        
        // 判断两条线段是否重叠
        static boolean isOverlap(Segment a, Segment b) {
            if (a.horizontal == true && b.horizontal == true && a.y1 == b.y1) {
                // 1. 两条都是水平线,且纵坐标相同
                if ((a.x1 >= b.x1 && a.x1 <= b.x2) || (a.x2 >= b.x1 && a.x2 <= b.x2) || (b.x1 >= a.x1 && b.x1 <= a.x2) || (b.x2 >= a.x1 && b.x2 <= a.x2)) {
                    return true;
                }
            } else if (a.horizontal == false && b.horizontal == false && a.x1 == b.x2) {
                // 2. 两条都是垂直线,且横坐标相同
                if ((a.y1 >= b.y1 && a.y1 <= b.y2) || (a.y2 >= b.y1 && a.y2 <= b.y2) || (b.y1 >= a.y1 && b.y1 <= a.y2) || (b.y2 >= a.y1 && b.y2 <= a.y2)) {
                    return true;
                }
            }
            
            return false;
        }

        // 联合两条线段,变成一条长的线段
        static Segment combine(Segment a, Segment b) {
            if (a.horizontal == true && b.horizontal == true) {
                int x1 = Math.min(a.x1, b.x1);
                int x2 = Math.max(a.x2, b.x2);
                int y = a.y1;
                return new Segment(x1, y, x2, y);
            } else {
                int x = a.x1;
                int y1 = Math.min(a.y1, b.y1);
                int y2 = Math.max(a.y2, b.y2);
                return new Segment(x, y1, x, y2);
            }
        }

        // 求两条相交线的交点
        static Point intersect(Segment a, Segment b) {
            // 若两条线段都为水平或垂直,则不可能相交
            if (a.horizontal == b.horizontal) {
                return null;
            }
            
            // 若a为垂直线b为水平线,交换两条线,使得a为水平线b为垂直线
            if (a.horizontal == false) {
                Segment temp = a;
                a = b;
                b = temp;
            }
            
            // a为水平线b为垂直线,只有相交的情况下,才有交点
            if (a.y1 >= b.y1 && a.y1 <= b.y2 && b.x1 >= a.x1 && b.x1 <= a.x2) {
                return new Point(b.x1,a.y1);
            }
            
            return null;
        }
    }

    
    // 内部类:点
    static class Point implements Comparable<Point> {
        int x;
        int y;
        ArrayList<Integer> adjacents;
        boolean visited;

        public Point(int X, int Y) {
            this.x = X;
            this.y = Y;
            this.adjacents = new ArrayList<Integer>();
            this.visited = false;
        }

        public boolean equals(Object obj) {
            Point another = (Point) obj;
            if (this.x == another.x && this.y == another.y) {
                return true;
            } else {
                return false;
            }
        }

        public int compareTo(Point another) {
            // 先按横坐标升序,若横坐标相同,按纵坐标升序
            if (this.x != another.x) {
                return this.x - another.x;
            } else {
                return this.y - another.y;
            }
        }
    }
}




更多内容请关注微信公众号


wechat.jpg
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,835评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,598评论 1 295
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,569评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,159评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,533评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,710评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,923评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,674评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,421评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,622评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,115评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,428评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,114评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,097评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,875评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,753评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,649评论 2 271

推荐阅读更多精彩内容