• SRM144 DIV1 1100


    这是一道很有意思的图论题,分析本问题之前必须先理解一笔画问题的原理,不难扩展出以下定理:

    若一个连通无向图的奇顶点个数为n

    (1) 当n为0时,图可以一笔画(且可以构造一个回路)

    (2) 其他情况,图可以n/2笔画(注意n一定为偶数)

    对于多联通分量的无向图,每个联通分量单独计算k笔画的情况,最后累加即可

    对于本题,将线段看成图的边,端点和交点看成图的顶点,画n遍转换为每条边重复出现至n次,而:笔抬起的次数=笔画数-1,这样题目就转化为下面这样的问题:

    给定一个图,求最少可以几笔画完,每条边不能重复也不能遗漏

    模型建好了,但是在编程计算的过程中还有以下难点:

    1. 如何处理互有覆盖(overlap)的线段?

    题目里已经很清楚的指出覆盖的区域也应该只被画一次,所以必须对这种情况进行处理:将所有有覆盖的线段合并成一个更长的线段,以保证没有任意两条线段再重叠,详见代码。

    2. 如何划分出各个联通子图?

    按照定理,必须先划分出联通子图分别求解才能计算出总的笔数,不能默认大图一定联通。可以按照基本的图的数据结构来计算,求出所有的交点和端点,判断点和点之间的连接情况,再对图进行一个搜索按连通性对节点进行划分,可想而知这样会相当繁琐。考虑到原图的基本元素就是线段,若划分出了线段也就划分出了联通图,所以可以根据线段的相交情况进行划分,这里需要用到一个数据结构并查集

    3. 如何计算每个联通子图的奇顶点的个数?

    同上,用基本的图模型来计算是相当繁琐的,考虑到交点的生成受到线段相交性(线段相交的情况)的影响,可以进行如下分析:

    两条线段相交只有三种情况:十字交(╋)、丁字交(┳)、端点交(┏)

    已知图G和线段L,L不在G中,G的奇顶点个数为odd(G)。

    若把L加入到G,L与G的每一次相交都会给odd(G)带来如下变化:╋+0,┳+0,-2,同时odd(G)还要加上L的基础量2,即:

    ( egin{equation} egin{split} odd(G+L) &= 2 + odd(G) + sum_{相交x} f(x) end{split} end{equation} )

    ( f(x) = -2(端点交)  or  0(其他) )

     实际上,在2中用并查集求联通分量的时候就可以一并把奇顶点个数计算出来,但在配合并查集的时候上述公式需要做一点变换,易推,详见代码。

      1 class UnionSet:
      2     def __init__(self, segs):
      3         self.gs = [Graph(seg) for seg in segs]  
      4         
      5     def union(self, x, y, q):
      6         i = self.find(x)
      7         j = self.find(y)
      8         if i != j:
      9             self.gs[i].extend(self.gs[j])
     10             self.gs[i].oddNodeCount += q + self.gs[j].oddNodeCount
     11             self.gs.pop(j)
     12         else:
     13             self.gs[i].oddNodeCount += q
     14             
     15     def find(self, x):
     16         for i in range(0, len(self.gs)):
     17             if x in self.gs[i]:
     18                 return i
     19         raise Exception()
     20     
     21     def graphs(self):
     22         return self.gs
     23         
     24         
     25 class Graph(list):
     26 
     27     def __init__(self, seg):
     28         list.__init__([])
     29         self.oddNodeCount = 2
     30         self.append(seg)
     31         
     32     def penCount(self, n):
     33         if n % 2 == 0:
     34             return 1
     35         if self.oddNodeCount == 0:
     36             return 1
     37         else:
     38             return int(self.oddNodeCount / 2)
     39             
     40             
     41 class Segment:
     42     def __init__(self, segStr=None, point=None):
     43         if segStr:
     44             sp = segStr.split(' ')
     45             self.x1 = int(sp[0])
     46             self.y1 = int(sp[1])
     47             self.x2 = int(sp[2])
     48             self.y2 = int(sp[3])
     49         else:
     50             self.x1 = point[0]
     51             self.y1 = point[1]
     52             self.x2 = point[2]
     53             self.y2 = point[3]
     54             
     55         if self.x1 == self.x2:
     56             self.isHor = False
     57             if self.y1 > self.y2:
     58                 temp = self.y1
     59                 self.y1 = self.y2
     60                 self.y2 = temp
     61         else:
     62             self.isHor = True
     63             if self.x1 > self.x2:
     64                 temp = self.x1
     65                 self.x1 = self.x2
     66                 self.x2 = temp
     67         
     68     def cross(self, seg):
     69         if self.isHor == seg.isHor:
     70             return 4
     71         if self.isHor:
     72             seg1 = self
     73             seg2 = seg
     74         else:
     75             seg1 = seg
     76             seg2 = self
     77 
     78         if seg1.x1 <= seg2.x1 <= seg1.x2 and seg2.y1 <= seg1.y1 <= seg2.y2:
     79             if seg2.x1 in [seg1.x1, seg1.x2] and seg1.y1 in [seg2.y1, seg2.y2]:
     80                 return 3
     81             elif seg2.x1 in [seg1.x1, seg1.x2] or seg1.y1 in [seg2.y1, seg2.y2]:
     82                 return 2
     83             else:
     84                 return 1
     85                 
     86         else:
     87             return 4
     88 
     89     def isoverlap(self, seg):
     90         if self.isHor != seg.isHor:
     91             return False
     92         if self.isHor and self.y1 == seg.y1:
     93             return self.x1 <= seg.x1 <= self.x2 or self.x1 <= seg.x2 <= self.x2 or seg.x1 <= self.x1 <= seg.x2 or seg.x1 <= self.x2 <= seg.x2
     94         elif not self.isHor and self.x1 == seg.x1:
     95             return self.y1 <= seg.y1 <= self.y2 or self.y1 <= seg.y2 <= self.y2 or seg.y1 <= self.y1 <= seg.y2 or seg.y1 <= self.y2 <= seg.y2
     96 
     97     def overlap(self, seg):
     98         x1 = min(self.x1, seg.x1, self.x2, seg.x2)
     99         x2 = max(self.x1, seg.x1, self.x2, seg.x2)
    100         y1 = min(self.y1, seg.y1, self.y2, seg.y2)
    101         y2 = max(self.y1, seg.y1, self.y2, seg.y2)
    102         return Segment(point = (x1,y1,x2,y2))
    103     
    104 class PenLift:
    105     def _combineSegments(self, segments):
    106         ss = [Segment(s) for s in segments]
    107         i = 0
    108         while i < len(ss):
    109             j = i + 1
    110             while j < len(ss):
    111                 if ss[i].isoverlap(ss[j]):
    112                     ss[i] = ss[i].overlap(ss[j])
    113                     ss.pop(j)
    114                 else:
    115                     j = j + 1
    116             i = i + 1
    117         return ss
    118                 
    119     def numTimes(self, segments, n):
    120         # 线段排重
    121         ss = self._combineSegments(segments) # 合并过的线段
    122         
    123         # 划分连通图, 直接通过线段计算(并查集)
    124         # 计算每个连通图点的总度数
    125         u = UnionSet(ss)
    126 
    127         for i in range(0, len(ss)):
    128             for j in range(i+1, len(ss)):
    129                 result = ss[i].cross(ss[j])
    130                 if result == 1:
    131                     #十字交
    132                     u.union(ss[i], ss[j], 0) 
    133                 elif result == 2:
    134                     #丁字交
    135                     u.union(ss[i], ss[j], 0)
    136                 elif result == 3:
    137                     #端点交
    138                     u.union(ss[i], ss[j], -2)
    139                 else:
    140                     #不相交
    141                     pass
    142 
    143         gs = u.graphs() # 子图
    144 
    145         # 每个连通图分别计算 
    146         sum = 0
    147         for g in gs:
    148             sum += g.penCount(n)
    149         return sum - 1
    View Code

    代码错了一个点:test case 74,原因不明。以上思路基本应该是正确的,不知道是不是哪种特殊情况没考虑到。

  • 相关阅读:
    Kvm --01 虚拟化基础概念
    Git--09 创建Maven项目
    Git--08 Jenkins
    Git--07 Gitlab备份与恢复
    Git --06 Git-gui安装
    Git --05 Gitlab使用
    Git--04 Github使用
    socket 释放全过程
    动态规划习题总结
    linux heap堆分配
  • 原文地址:https://www.cnblogs.com/valaxy/p/3391661.html
Copyright © 2020-2023  润新知