0w1

GCJ 2017 Qual D. Fashion Show ( Bipartite Matching )

Dashboard - Qualification Round 2017 - Google Code Jam

題意:
給 N * N 的棋盤,以及一些士兵。士兵有三種 : { 'x', '+', 'o' },若沒有士兵用 '.' 描述。一個格子至多一個士兵。士兵配置的規則如下:
1. 任意兩個在同個水平或鉛直線上的士兵,必須有其中一個是 '+'。
2. 任意兩個在同個正斜線上的士兵,必須有其中一個是 'x'。
你可以進行兩種操作:
1. 將已存在的士兵由 'x' 或 '+' 升格為 'o'。
2. 將任意一種士兵配置在空的格子上。
問最佳操作,最大化棋盤的分數。
棋盤的分數是 'x' 的數量 + '+' 的數量 + 'o' 的兩倍數量。

制約:
1 ≤ T ≤ 100.
1 ≤ N ≤ 100.
1 ≤ Ci ≤ N, for all i.
0 ≤ M ≤ N^2.

解法:
首先將 'o' 視為 '+' 和 'x' 的重疊。
接著考慮將這個棋盤分為兩個獨立的棋盤,一個只有 '+',一個只有 'x'。
換個說法,題意表示任意兩個 '+' 不能同時出現在一個正斜線上,同時,任意兩個 'x' 不能同時出現在一個水平或鉛直線上。由於問題是獨立的,只要分開求最大化即可。這兩個都是經典問題,前者是最大化 bishop 在棋盤上不衝突的數量,後者是最大化 rook 在棋盤上不衝突的數量。後者只要輕鬆貪心即可,因為一個 rook 不多不少只會剛好佔據一個鉛直線和一個水平線,怎麼做都會有最大數量 N。前者可以用二分圖匹配解決,由於一個配置 ( x, y ) 可以用兩方向的各一個唯一確定的正斜線描述,以正斜線為節點,左上至右下為一邊的集合,右上至左下為另一邊的集合,有交點的節點間連邊,剛好帶有二分圖的特性。
話說我不太確定這個匹配算法的複雜度究竟是...? 看起來至少平均情況大約 N^3。
另外,雖然跟這題沒關係,不過原來在 N * N 的棋盤上任意配置 bishop 時,最佳配置是這樣,而且最大匹配數是 N - 2。

時間 / 空間複雜度:
O( N^3 ) / O( N^2 )

def match( u, edge, gf, bf, vis = None ):
  if vis == None:
    vis = [ False for i in range( len( edge ) ) ]
  for v in edge[ u ]:
    if vis[ v ]: continue
    vis[ v ] = True
    if bf[ v ] == -1 or match( bf[ v ], edge, gf, bf, vis ):
      bf[ v ] = u
      gf[ u ] = v
      return True
  return False

T = int( input() )
for ti in range( 1, T + 1, 1 ):
  N, M = map( int, input().split() )
  G = [ [ '.' for i in range( N ) ] for j in range( N ) ] # original graph
  nG = [ [ '.' for i in range( N ) ] for j in range( N ) ] # modifying graph
  for mi in range( M ):
    f, x, y = input().split()
    x, y = int( x ) - 1, int( y ) - 1
    x, y = map( int, [ x, y ] )
    G[ x ][ y ], nG[ x ][ y ] = f, f
  # solve rook
  vis_x = [ False for i in range( N ) ]
  vis_y = [ False for i in range( N ) ]
  for i in range( N ):
    for j in range( N ):
      if G[ i ][ j ] == 'x' or G[ i ][ j ] == 'o':
        vis_x[ i ], vis_y[ j ] = True, True
  for i in range( N ):
    for j in range( N ):
      if vis_x[ i ] or vis_y[ j ]: continue
      vis_x[ i ], vis_y[ j ] = True, True
      if nG[ i ][ j ] == '.':
        nG[ i ][ j ] = 'x'
      elif nG[ i ][ j ] == '+':
        nG[ i ][ j ] = 'o'
  # rook solved
  # solve bishop with bipartite matching
  used_4 = [ False for i in range( 2 * N - 1 ) ] # top-left to bottom-right
  used_6 = [ False for i in range( 2 * N - 1 ) ] # top-right to bottom-left
  for i in range( N ):
    for j in range( N ):
      if nG[ i ][ j ] == '+' or nG[ i ][ j ] == 'o':
        used_4[ N - 1 + j - i ], used_6[ i + j ] = True, True
  edge = [ [] for i in range( 2 * N - 1 ) ]
  for i in range( 2 * N - 1 ):
    if used_4[ i ]: continue
    for j in range( 2 * N - 1 ):
      if used_6[ j ]: continue
      yy = i + j - ( N - 1 )
      if yy & 1: continue
      x, y = j - yy // 2, yy // 2 # intersection
      if not ( 0 <= x and x < N and 0 <= y and y < N ): continue
      edge[ i ].append( j )
  gf = [ -1 for i in range( 2 * N - 1 ) ]
  bf = [ -1 for i in range( 2 * N - 1 ) ]
  for i in range( 2 * N - 1 ):
    match( i, edge, gf, bf )
  for i in range( 2 * N - 1 ):
    if gf[ i ] == -1: continue
    y = i + gf[ i ] - ( N - 1 ) >> 1
    x = gf[ i ] - y
    if nG[ x ][ y ] == '.':
      nG[ x ][ y ] = '+'
    elif nG[ x ][ y ] == 'x':
      nG[ x ][ y ] = 'o'
  # evaluate score and print difference
  scr, diff = 0, 0
  for i in range( N ):
    for j in range( N ):
      diff += G[ i ][ j ] != nG[ i ][ j ]
      if nG[ i ][ j ] == '+' or nG[ i ][ j ] == 'x':
        scr += 1
      elif nG[ i ][ j ] == 'o':
        scr += 2
  print( "Case #%d: %d %d" % ( ti, scr, diff ) )
  for i in range( N ):
    for j in range( N ):
      if G[ i ][ j ] == nG[ i ][ j ]: continue
      print( "%c %d %d" % ( nG[ i ][ j ], i + 1, j + 1 ) )