Ford-Fulkerson 方法——最大流問題

最大流&&最小費用最大流&&最大二分匹配

中文是2017年8月的筆記,英文是2018.11月的筆記

英文筆記來自于MIT公開課的筆記,教材為Introduction to Algorithm(Third Edition),根據四位作者姓名首字母大寫常稱作CLRS,以下是一些資源:

最大流問題

  • 比喻:有一個自來水管道運輸系統,起點是 s,終點是 t,途中經過的管道都有一個最大的容量,可以想象每條管道不能被水流“撐爆”。求從 s 到 t 的最大水流量是多少?

  • 應用:網絡最大流問題是網絡的另一個基本問題。許多系統包含了流量問題。例如交通系統有車流量,金融系統有現金流,控制系統有信息流等。許多流問題主要是確定這類系統網絡所能承受的最大流量以及如何達到這個最大流量。

  • 流網絡(Flow Networks):指的是一個有向圖 G = (V, E),其中每條邊 (u, v) ∈ E 均有一非負容量 c(u, v) ≥ 0。如果 (u, v) ? E 則可以規定 c(u, v) = 0。流網絡中有兩個特殊的頂點:源點 s (source)和匯點 t(sink)。為方便起見,假定每個頂點均處于從源點到匯點的某條路徑上,就是說,對每個頂點 v ∈ E,存在一條路徑 s --> v --> t。

  • 容量限制(Capacity constraint):對于所有的結點 u, v ∈ V,要求 0 ≤ f(u, v) ≤ c(u, v)

  • 流量限制/流量守恒(Flow conservation):對于所有的結點 u ∈ V - {s, t},要求 Σf(v, u) = Σf(u, v)

    We call this property "flow conservation", and it is equivalent to Kirchhoff's current law when the material is electrical current.

    Flow in equals flow out.

  • 當(u, v) ? E時,從結點 u 到結點 v 之間沒有流,因此f(u, v) = 0。我們稱非負數值f(u, v)為從結點 u 到結點 v 的流,定義如下: |f| = Σf(s, v) - Σf(v, s),也就是說,流 f 的值是從源結點流出的總流量減去流入源結點的總流量。(有點類似電路中的基爾霍夫定律)

    Here, the |*|notation denotes flow value, not absolute value or cardinality.

具有多個源結點和多個匯點的網絡

  • 一個最大流問題可能會包含幾個源結點和幾個匯點,比如{s1, s2, ..., sm} 以及 {t1, t2, ..., tm},而不僅僅只有一個源結點和匯點,其解決方法并不比普通的最大流問題難。
  • 加入一個超級源結點 s,并對于多個源結點,加入有向邊 (s, si) 和容量 c(s, si) = ∞,同時創建一個超級匯點 t,并對于多個匯點,加入有向邊 (ti, t) 和容量 c(ti, t) = ∞。
  • 這樣單源結點能夠給原來的多個源結點 si 提供所需要的流量,而單匯點 t 則可以消費原來所有匯點 ti 所消費的流量。

Ford-Fulkerson 方法

We call it a “method” rather than an “algorithm” because it encompasses
several implementations with differing running times.

  • 幾個重要的概念

    • 殘留網絡(residual capacity):容量網絡 - 流量網絡 = 殘留網絡

      • 具體說來,就是假定一個網絡 G =(V,E),其源點 s,匯點 t。設 f 為 G 中的一個流,對應頂點 u 到頂點 v 的流。在不超過 C(u,v)的條件下(C 代表邊容量),從 u 到 v 之間可以壓入的額外網絡流量,就是邊(u,v)的殘余容量(residual capacity)。

      • 殘余網絡 Gf 還可能包含 G 中不存在的邊,算法對流量進行操作的目的是增加總流量,為此,算法可能對特定邊上的流量進行縮減。為了表示對一個正流量 f(u ,v) 的縮減,我們將邊 (u, v) 加入到 Gf中,并將其殘余容量設置為 cf(v, u) = f(u ,v)。也就是說,一條邊所能允許的反向流量最多能將其正向流量抵消。

      • 殘存網絡中的這些反向邊允許算法將已經發送出來的流量發送回去。而將流量從同一邊發送回去等同于縮減該邊的流量,這種操作在很多算法中都是必需的。

    • 增廣路徑(augmenting path): 這是一條不超過各邊容量的從 s 到 t 的簡單路徑,向這個路徑注入流量,可以增加整個網絡的流量。我們稱在一條增廣路徑上能夠為每條邊增加的流量的最大值為路徑的殘余容量,cf(p) = min{cf(u,v) : (u,v)∈路徑p}

    • 割:用來證明 “當殘留網絡中找不到增廣路徑時,即找到最大流”,最大流最小切割定理,具體證明略。

  • 算法過程:

    • 開始,對于所有結點 u, v ∈ V, f(u, v) = 0,給出的初始流值為0。

    • 在每一次迭代中,將 G 的流值增加,方法就是在殘留網絡 Gf 中尋找一條增廣路徑(一般用 BFS 算法遍歷殘留網絡中各個結點,以此尋找增廣路徑),然后在增廣路徑中的每條邊都增加等量的流值,這個流值的大小就是增廣路徑上的最大殘余流量。

    • 雖然 Ford-Fulkerson 方法每次迭代都增加流值,但是對于某條特定邊來說,其流量可能增加,也可能減小,這是必要的,詳情見下文的“反向邊”。

    • 重復這一過程,直到殘余網絡中不再存在增廣路徑為止。最大流最小切割定理將說明在算法終結時,該算法獲得一個最大流。

    • 偽代碼:

      FORD-FULKERSON(G,t,s)
      
      1 for each edge(u,v) 屬于 E(G)
      
      2     do f[u,v]=0
      
      3          f[v,u]=0
      
      4 while there exists a path p from s to t in the residual network Gf // 根據最大流最小切割定理,當不再有增廣路徑時,流 f 就是最大流
      
      5       do cf(p)=min{cf(u,v):(u,v)is in p}  // cf(p)為該路徑的殘余容量
      
      6        for each edge (u,v) in p
      
      7              do f[u,v]=f[u,v]+cf(p)  //為該路徑中的每條邊中注入剛才找到到的殘余容量
      
      8                    f[v,u]=-f[u,v]   //反向邊注入反向流量
      
    • 反向邊是什么?

      轉自(已失效-_-):http://nano9th.wordpress.com.cn/2009/02/17/%E7%BD%91%E7%BB%9C%E6%B5%81%E5%9F%BA%E7%A1%80%E7%AF%87-edmond-karp%E7%AE%97%E6%B3%95/

      • 假設沒有上面偽代碼中最后一步的操作,那么對于如下的流網絡:

        image.png
      • 我們第一次找到了 1-2-3-4 這條增廣路,這條路上的最小邊剩余流量顯然是 1。于是我們修改后得到了下面這個殘留網絡:

        image.png
      • 這時候 (1,2) 和 (3,4) 邊上的流量都等于容量了,我們再也找不到其他的增廣路了,當前的流量是 1。但這個答案明顯不是最大流,因為我們可以同時走 1-2-4 和 1-3-4,這樣可以得到流量為 2 的流。

      • 這是貪婪算法行不通的一個例子(摘自《算法與數據結構 C++語言描述》第四版,但翻譯很差勁,推薦讀原版)

      • 而這個算法神奇的利用了一個叫做反向邊的概念來解決這個問題。即每條邊 (i,j) 都有一條反向邊 (j,i),反向邊也同樣有它的容量。那么我們剛剛的算法問題在哪里呢?問題就在于我們沒有給程序一個后悔的機會,應該有一個不走 (2-3-4) 而改走 (2-4) 的機制。

      • 我們來看剛才的例子,在找到 1-2-3-4 這條增廣路之后,把容量修改成如下:

        image.png
      • 這時再找增廣路的時候,就會找到 1-3-2-4 這條可增廣量,即 delta 值為 1 的可增廣路。將這條路增廣之后,得到了最大流 2。

        image.png
      • 解釋:

        事實上,當我們第二次的增廣路走 3-2 這條反向邊的時候,就相當于把 2-3 這條正向邊已經是用了的流量給” 退” 了回去,不走 2-3 這條路,而改走從 2 點出發的其他的路也就是 2-4。(有人問如果這里沒有 2-4 怎么辦,這時假如沒有 2-4 這條路的話,最終這條增廣路也不會存在,因為他根本不能走到匯點)同時本來在 3-4 上的流量由 1-3-4 這條路來” 接管”。而最終 2-3 這條路正向流量 1,反向流量 1,等于沒有流量。

      • 這就是這個算法的精華部分,利用反向邊,使程序有了一個后悔和改正的機會

        The intuition behind this definition(反向邊) follows the definition of the residual network.
        We increase the flow on (u, v) by f'(u, v) but decrease it by f'(u, v) because pushing flow on the reverse edge in the residual network signifies decreasing the flow in the original network.

        Pushing flow on the reverse edge in the residual network is also known as cancellation.

      • 關于反向邊的討論——Modeling problems with antiparallel edges

        • Our original assumption: if an edge (v1, v2) ∈ E, then (v2, v1) ? E.

        • What if we violate this assumption? Thus, we have (v2, v1) ∈ E. We call the two edges (v1, v2) and (v2, v1) antiparallel.

        • How to solve this problem? We could convert a graph with antiparallel edges into a graph wihout antiparallel edges:

          • Split (v1, v2) by adding a new vertex v' and replacing edge (v1, v2) with the pair of edge (v1, v') and (v', v2).
          • Set the capacity of both new edges to the capacity of the original edge.
          • Thus, the resulting network satisfies the property that if an edge is in the network, the reverse is not.
        • 總之,antiparallel是允許存在的,引入一個新節點即可把帶antiparallel的圖轉換為不帶antiparallel的圖

最大流-最小割定理

最大流-最小割定理用來證明Ford-Fulkson方法的確達到了最大流

Cuts of flow networks

  • The Ford-Fulkerson method repeatedly augments the flow along augmenting paths until it has found a maximum flow.

    How do we know that when the algorithm terminates, we have actually found a maximum flow?

    The max-flow min-cut theorem, which we shall prove shortly, tells us that a flow is maximum if and only if its residual network contains no augmenting path.

    To prove this theorem, though, we must first explore the notion of a cut of a flow network.

  • You can overview cut theory in my preview note of minimum spanning tree.

  • Definition:

    • If f is a flow, then the net flow f(S, T) across the cut (S, T) is defined to be

      f(S, T) = Σu∈SΣv∈T f(u, v) - Σu∈SΣv∈T f(v, u)

    • The capacity of the cut (S, T) is

      c(S, T) = Σu∈SΣv∈T c(u, v)

    • A minimum cut of a network is a cut whose capacity is minimum over all cuts of the network.

    • Let f be a flow in a flow network G with source s and sink t, and let (S, T) be any cut of G. Then the net flow across (S, T) is

      f(S, T) = |f|

    • The value of any flow f in a flow network G is bounded from above by the capacity of any cut of G

  • Theorem 26.6 (Max-flow min-cut theorem)
    If f is a flow in a flow network G(V, E) with source s and sink t, then the
    following conditions are equivalent:

    1. f is a maximum flow in G.
    2. The residual network Gf contains no augmenting paths.
    3. |f| = c(S, T) for some cut (S, T) of G.

算法的效率及其優化—— Edmonds-Karp 算法

  • 如果使用廣度優先搜索(BFS)來尋找增廣路徑,那么可以改善 FORD-FULKERSON 算法的效率,也就是說,每次選擇的增廣路徑是一條從 s 到 t 的最短路徑,其中每條邊的權重為單位距離(即根據邊的數量來計算最短路徑),我們稱如此實現的 FORD-FULKERSON 方法為 Edmonds-Karp 算法。其運行時間為 O(VE^2)。

  • 注意 E-K 算法適用于改善 F-F 算法的效率,邊的權重僅僅還是容量限制,而下文的“最小費用最大流”中的每條邊的權重有兩個值:(容量限制,單位流量損耗)。

最大流實例

  • 對于如下拓撲圖,給出從S1到S6允許的流的方向和帶寬限制:

    • 求出S1到S6最大可能帶寬(提示Ford-Fulkerson算法)。

    • 畫出流的流向及帶寬分配,使達到最大可能的帶寬。

    image.png
  • 根據算法,最大流的值為23(定值),而下圖是一種可能的流量走向:

    image.png
  • 源碼:https://github.com/edisonleolhl/DataStructure-Algorithm/blob/master/Graph/MaxFlow/maxflow.py

  • 在尋找增廣路徑時用到了 BFS 算法,以后有時間再寫寫 BFS、DFS 的文章,注意用到了 Python 中的標準庫:deque,這是雙端隊列。

CLRS Exercies

本節摘錄了一些算法導論上的對應習題

26.1-5

  • State the maximum-flow problem as a linear-programming problem.

  • Solution:

    max ∑f(s, v) - ∑f(v,s)

    s.t. 0 ≤ f(u, v) ≤ c(u, v)

    ? ∑f(v, u) - ∑f(u, v) = 0

26.1-6

  • Professor Adam has two children who, unfortunately, dislike each other. The problem is so severe that not only do they refuse to walk to school together, but in fact each one refuses to walk on any block that the other child has stepped on that day. The children have no problem with their paths crossing at a corner. Fortunately both the professor's house and the school are on corners, but beyond that he is not sure if it is going to be possible to send both of his children to the same school. The professor has a map of his town. Show how to formulate the problem of determining whether both his children can go to the same school as a maximum-flow problem.

  • Solution:

    Create a vertex for each corner, and if there is a street between corners u and v, create directed edges (u,v) and (v,u).

    Set the capacity of each edge to 1. Let the source be corner on which the professor's house sits, and let the sink be the corner on which the school is located.

    We wish to find a flow of value 22 that also has the property that f(u,v) is an integer for all vertices u and v.

    Such a flow represents two edge-disjoint paths from the house to the school.

26.1-7

  • Suppose that, in addition to edge capacities, a flow network has vertex capacities. That is each vertex vv has a limit l(v) on how much flow can pass though vv. Show how to transform a flow network G=(V,E) with vertex capacities into an equivalent flow network G′=(V′,E′) without vertex capacities, such that a maximum flow in G′ has the same value as a maximum flow in G. How many vertices and edges does G′ have?

  • Solution:

    We will construct G′ by splitting each vertex v of G into two vertices v1, v2, joined by an edge of capacity l(v). All incoming edges of vv are now incoming edges to v1. All outgoing edges from vv are now outgoing edges from v2.

    More formally, construct G′=(V′,E′) with capacity function c′ as follows. For every v∈V, create two vertices v1, v2 in V′. Add an edge (v1,v2) in E′ with c′(v1,v2)=l(v). For every edge (u,v)∈E, create an edge (u2,v1) in E′ with capacity c′(u2,v1)=c(u,v). Make s1 and t2 as the new source and target vertices in G′. Clearly, |V′|=2|V| and |E′|=|E|+|V|.

    I draw a picture to vividly illustrate the idea.

    image.png

最小費用最大流

  • 最小費用最大流問題是經濟學和管理學中的一類典型問題。在一個網絡中每段路徑都有 “容量” 和 “費用” 兩個限制的條件下,此類問題的研究試圖尋找出:流量從 A 到 B,如何選擇路徑、分配經過路徑的流量,可以在流量最大的前提下,達到所用的費用最小的要求。如 n 輛卡車要運送物品,從 A 地到 B 地。由于每條路段都有不同的路費要繳納,每條路能容納的車的數量有限制,最小費用最大流問題指如何分配卡車的出發路徑可以達到費用最低,物品又能全部送到。

  • 注意:最后得到的流必須是最大流,最大流可能有多種情況,目標是找出最小費用的那種情況。

  • 解決最小費用最大流問題,一般有兩條途徑。

    • 一條途徑是先用最大流算法算出最大流,然后根據邊費用,檢查是否有可能在流量平衡的前提下通過調整邊流量,使總費用得以減少?只要有這個可能,就進行這樣的調整。調整后,得到一個新的最大流。然后,在這個新流的基礎上繼續檢查,調整。這樣迭代下去,直至無調整可能,便得到最小費用最大流。這一思路的特點是保持問題的可行性(始終保持最大流),向最優推進。

    • 另一條解決途徑和前面介紹的最大流算法思路相類似,一般首先給出零流作為初始流。這個流的費用為零,當然是最小費用的。然后尋找一條源點至匯點的增流鏈,但要求這條增流鏈必須是所有增流鏈中費用最小的一條。如果能找出增流鏈,則在增流鏈上增流,得出新流。將這個流做為初始流看待,繼續尋找增流鏈增流。這樣迭代下去,直至找不出增流鏈,這時的流即為最小費用最大流。這一算法思路的特點是保持解的最優性(每次得到的新流都是費用最小的流),而逐漸向可行解靠近(直至最大流時才是一個可行解)。

  • 第二種辦法與前文的 Ford-fulkerson 方法很像,所以選擇它更方便,如何找到費用最小的增鏈流呢?可以用最短路徑算法,這里是單源最短路徑,所以選擇 Dijkstra 算法找出最短路徑即可,關于 Dijkstra 的介紹見:http://www.lxweimin.com/p/8ba71199a65f,里面有 Python 實現的程序。

最小費用最大流實例

  • 對于如下拓撲圖,給出從S1到S6允許的流的方向和帶寬限制,鏈路按帶寬收費,以括號形式表示為(帶寬容量,單位帶寬費用):

    • 求出S1到S6最小費用下最大可能帶寬,得出最小費用值,并標出選路狀況。

    • 寫出對給出任意拓撲圖的通用算法描述。

      image.png
  • 源碼:https://github.com/edisonleolhl/DataStructure-Algorithm/blob/master/Graph/MaxFlow/mincostmaxflow.py

  • 運行截圖:

    image.png
  • 注意增廣路徑是回溯的,比如第一條增廣路徑,終點為5,path[5]=4,所以它的前驅是4,path[4]=2,所以4的前驅是2,2的前驅是1,1的前驅是0,所以這條路徑是 0-1-2-4-5,也就是 s1-s2-s3-s5-s6。

  • 注意在尋找增廣路徑時用到了 Dijkstra 算法,至于為什么用 heapq (最小堆的實現),因為它每次 pop 出來的都是最小的項目,根據 Dijkstra ,每次要從未訪問點中找到最小距離的頂點,這樣就可以巧妙實現。

  • 流量分布情況:

    image.png

最大二分匹配

本節部分內容轉自:二分圖的最大匹配、完美匹配和匈牙利算法
、圖的匹配問題與最大流問題 (五)—— 計算二分圖的最大匹配

  • 最大匹配定義:給定一個無向圖 G = (V, E),一個匹配是指:E 的某個子集 M , 對于所有的結點 v ∈ V,子集 M 中最多有一條邊與 v 相連,如果子集 M 中的某條邊與 v 相連,那么稱 v 由 M 匹配;否則 v 就是沒有匹配的。最大匹配是指:對于所有任意匹配 M',有 |M| ≥ |M'| 的匹配 M 。

  • 二分圖定義:設 G=(V,E) 是一個無向圖,如果頂點 V 可分割為兩個互不相交的子集 (A,B),并且圖中的每條邊(i,j)所關聯的兩個頂點 i 和 j 分別屬于這兩個不同的頂點集 (i in A,j in B),則稱圖 G 為一個二分圖。

  • 完美匹配:如果一個圖的某個匹配中,所有的頂點都是匹配點,那么它就是一個完美匹配。顯然,完美匹配一定是最大匹配(完美匹配的任何一個點都已經匹配,添加一條新的匹配邊一定會與已有的匹配邊沖突)。但并非每個圖都存在完美匹配。

  • 應用:把機器集合 L 與任務集合 R 相匹配, E 中有邊 (u, v) 就說明一臺特定的機器 u ∈ L 能夠完成一項特定的任務 v ∈ R,最大二分匹配就是讓盡可能多的機器運行起來,因為一臺機器只能同時做一個任務,一個任務也只能同時被一個機器完成,所以這里也可理解為讓盡可能多的任務被完成。

  • 用下圖說明,圖 1 是二分圖,為了直觀,一般畫成 2 那樣,3、4 中紅色邊即為匹配,4 是最大匹配,同時也是完美匹配(所有頂點都是匹配點),圖 5 展示了男孩和女孩暗戀關系,有連線就說明這一對能成,求最大匹配就是求能成多少對

    image.png
    image.png
    image.png
    image.png
    image.png

Ford-Fulkson方法解決最大二分匹配

給定如下的二分圖(忽略顏色):

image.png

把已有的邊設為單向邊(方向 L -> R),且各邊容量設為 ∞ ;增加源結點 s 與匯點 t,將 s 與集合 L 中各個結點之間構造單向邊,且各邊容量設為 1;同樣的,將集合 R 中各個結點與 t 之間構造單向邊,且各邊容量設為 1。這時得到一個流網絡 G',如下:

image.png

這時,最大匹配數值就等于流網絡 G' 中最大流的值。

匈牙利算法解決二分匹配

推薦文章:趣寫算法系列之 -- 匈牙利算法

關鍵就是騰挪,迭代地騰挪

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,156評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,401評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,069評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,873評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,635評論 6 408
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,128評論 1 323
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,203評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,365評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,881評論 1 334
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,733評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,935評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,475評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,172評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,582評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,821評論 1 282
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,595評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,908評論 2 372

推薦閱讀更多精彩內容