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,即:

\( \begin{equation} \begin{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,原因不明。以上思路基本应该是正确的,不知道是不是哪种特殊情况没考虑到。

posted @ 2013-10-28 02:00  valaxy  阅读(250)  评论(0编辑  收藏  举报