線性規劃技巧: Dantzig&Wolfe Decompostion

Dantzig&Wolfe分解(簡稱DW分解)[1]是一種列生成技巧,可以把一類特殊形式線性規劃問題分解成若干子問題進行求解.

問題描述

我們考慮如下形式的線性規劃問題:

\begin{aligned} \min ~ & c_1^Tx_1 & + ~ & c_2^Tx_2 & + ~ & \cdots & + ~ & c^T_m x_m & \\ \text{s.t. } & A_{0,1}x_1 & + ~ & A_{0,2}x_2 & + ~ & \cdots & + ~& A_{0,m} x_m & = b_0 \\ & A_{1,1} x_1 & & & & & & & = b_1 \\ & & & A_{2,2} x_2 & & & & & = b_2 \\ & & & & & ... & & & \\ & & & & & & & A_{n,m}x_m & = b_m \\ & x_j \geq 0, & \forall j & \end{aligned}

其中A_{i,j}\in \mathbb{R}^{m_i \times n_j}, b_i \in\mathbb{R}^{m_i}, c_j, x_j \in \mathbb{R}^{n_j}.

說明

  1. 上述規劃共m+1組約束. 第一組約束包含了所有的決策變量, 稱為連接約束(Linking constraints), 接下來是m組獨立的約束.
  2. c_j, x_j, b_i都是向量.
  3. 上述規劃如果寫成標準形式\min_x \{c^Tx | Ax = b, x\geq 0\}, 其系數矩陣A的維度是\sum_{i=0}^m m_i \times \sum_{j=1}^n n_j. 我們發現A實際上是相對稀疏的. 當m, n的非常大時, 矩陣的規模可能大到無法直接求解上述規劃. 在這種情況下, 我們考慮把它分解成多個子問題進行求解(DW分解).

應用場景

例1 一個公司有m個部門, 各部門有獨立的約束, 部門之間也有約束. 目標是最小化所有部門的總成本.

例2 一個零售公司有m個倉庫, 需要決定每個倉庫存放的商品. 每個倉庫中對商品有一些約束. 商品關于各倉庫也需要滿足一些約束. 目標是最小化總的成本(例如配送成本/時效等).

主問題

P_i = \{ x_i \in \mathbb{R}^{n_i} \mid A_{i,i}x_i = b_i\}. 如果P_i是有界的, 那么x_i可以表示成P_i頂點的凸組合. 設v_{i,1}, v_{i,2}, \ldots, v_{i, p_i}代表P_i的頂點(Extreme point), 則存在\lambda_{i,j} \geq 0\sum_{j=1}^{p_i} \lambda_{i,j}=1使得
x_i = \sum_{j=1}^{p_i} \lambda_{i,j} v_{i,j}.
在一般情況下(P_i無界或有界時), 它可以用頂點和極射線的線性組合來表示(參考 Minkowski表示定理). 具體來說, 令w_{i,1}, w_{i,2}, \ldots, w_{i,q_i}代表極射線(Extreme ray). 則存在\lambda_{i,j} \geq 0\sum_{j=1}^{p_i} \lambda_{i,j}=1, \mu_{i,j}\geq 0使得
x_i = \sum_{j=1}^{p_i} \lambda_{i,j} v_{i,j} + \sum_{j=1}^{q_i}\mu_{i,j} w_{i,j}.

假設我們枚舉P_1, P_2, \ldots, P_m所有的頂點和極射線. 把上式代入原問題得到主問題(Master problem):

主問題

\begin{aligned} \min\ & \sum_{i=1}^m\sum_{j=1}^{p_i}\lambda_{i,j}(c_i^Tv_{i,j}) + \sum_{i=1}^m\sum_{j=1}^{q_i}\mu_{i,j}(c_i^Tw_{i,j}) \\ \text{s.t. } & \sum_{i=1}^m \sum_{j=1}^{p_i}\lambda_{i,j} A_{0,i}v_{i,j} + \sum_{i=1}^m \sum_{j=1}^{p_i}\mu_{i,j} A_{0,i}w_{i,j} = b_0 \\ & \sum_{j=1}^{p_i} \lambda_{i,j} =1, \quad \forall i \\ & \lambda_{i,j}, \mu_{i,j} \geq0, \quad \forall i, j. \end{aligned}

說明

  1. \lambda_{i,j}, \mu_{i,j}是決策變量.
  2. 約束的數量為m_0+m(第一個等式包含了m_0個約束). 原問題的約束數量是\sum_{i=0}^m m_i. 因此主問題的約束數量明顯減少了.
  3. 但是主問題的變量顯著增加了(\lambda_{i,j}, \mu_{i,j}對應所有的頂點和極射線). 與列生成技巧相似, 主問題一開始只考慮兩個可行解(對應頂點和極射線). 通過求解子問題得到新的頂點或極射線.
  4. 在實際問題中, 一般情況下P_i都是有界的. 此時主問題可以簡化成如下形式.
    \begin{aligned} \min\ & \sum_{i=1}^m\sum_{j=1}^{p_i}\lambda_{i,j}(c_i^Tv_{i,j}) \\ \text{s.t. } & \sum_{i=1}^m \sum_{j=1}^{p_i}\lambda_{i,j} A_{0,i}v_{i,j} = b_0 \\ & \sum_{j=1}^{p_i} \lambda_{i,j} =1, \quad \forall i \\ & \lambda_{i,j} \geq 0, \quad \forall i, j. \end{aligned}

子問題

定義主問題的對偶變量y\in\mathbb{R}^{m_0}, z_i \in \mathbb{R}, i=1, 2, \ldots, m. 我們計算變量\lambda_{i,j}, \mu_{i,j}Reduced Cost:

\begin{aligned} & \alpha_{i,j} = c_i^T v_{i,j} - y^TA_{0,i}v_{i,j} - z_i \\ & \beta_{i, j} = c_i^Tw_{i,j} - y^TA_{0,i}w_{i,j} \end{aligned}

其中\alpha_{i,j}, \beta_{i,j}分別代表\lambda_{i,j}, \mu_{i,j}的reduced cost. 當\alpha_{i,j}, \beta_{i,j}\geq 0時得到原問題的最優解. 因此在子問題中, 我們需要計算v_{i,j}(或w_{i,j})使得\alpha_{i,j}, \beta_{i,j}的值盡可能小.

子問題 - i

\begin{aligned} \min\ & f_{i} := (c_i - A_{0,i}^Ty)^T x_i \\ \text{s.t. } & A_{i,i} x_i = b_i \\ & x_i \geq 0. \end{aligned}

求解子問題時考慮三種情況.

  1. 最優解的目標值為-\infty. 此時\alpha_{i,j}, \beta_{i,j} < 0, 子問題的解是極射線w_{i,j}. 我們把A_{0,i}w_{i,j}加入到主問題進行求解.
  2. 最優解的目標值有界且f_i < z_i. 此時\alpha_{i,j} < 0, 子問題的解是頂點v_{i,j}. 我們把A_{0,i}v_{i,j}加入到主問題進行求解.
  3. 最優解的目標值有界且f_{i,j} \geq z_i. 此時0\leq \alpha_{i,j} \leq \beta_{i,j}(注意到實際上z_i\geq 0), 得到最優解.

例子: 一個選品問題

考慮m個零售品牌商, 每個零售品牌商有自己的商品(SKU), 例如可口可樂, 雪碧和芬達對應同一家品牌商. 一家電商平臺需要從m個品牌中選擇一些商品做營銷活動. 已知每個商品的營銷成本, 商品預期的收益和營銷的預算. 在總營銷費用不超過預算且每個品牌選中商品數量有限制的前提下, 如何選擇商品使得預期的收益最大化?

我們先考慮一個直觀的數學模型.

indices

  • i -- 品牌
  • j -- 商品

parameters

  • a_{i, j} \in \{0, 1\} -- 商品j是否屬于品牌i
  • p_j -- 商品j的預期收益
  • c_j -- 商品j的營銷成本
  • b_i -- 選中品牌i的商品數量上限
  • d -- 營銷費用的總預算

decision variables

  • x_j\in \{0,1\} -- 是否選擇商品j

模型1

\begin{aligned} \max\ & \sum_j p_j x_j \\ \text{s.t. } & \sum_j c_j x_j \leq d \\ & \sum_j a_{i,j} x_j \leq b_i, \quad \forall i\\ & x_j \in \{0, 1\}. \end{aligned}

當品牌和商品數量較大時, 例如1000品牌和10萬商品, 這時參數a_{i,j}的規模是1億, 因此直接求解非常困難. 注意到每個品牌的商品是不一樣的, 因而矩陣A = (a_{i,j})非常稀疏, 我們可以把每個品牌中的商品分開考慮, 得到如下模型.

indices

  • i -- 品牌
  • j -- 商品

parameters

  • n_i -- 品牌i的商品數量
  • p_{i,j} -- 品牌i中商品j的預期收益, j=1, 2, \ldots, n_i
  • c_{i,j} -- 品牌i中商品j的營銷成本, j=1, 2, \ldots, n_i
  • b_i -- 品牌i可以被選中的商品數量上限
  • d -- 營銷費用的總預算

decision variables

  • x_{i,j}\in \{0, 1\} -- 是否選擇品牌i中的商品j, j=1, 2, \ldots, n_i

模型2
\begin{aligned} \max\ & \sum_{i=1}^m\sum_{j=1}^{n_i} p_{i,j} x_{i,j} \\ \text{s.t. } & \sum_{i=1}^m\sum_{j=1}^{n_i} c_{i,j} x_{i,j} \leq d \\ & \sum_{j=1}^{n_i} x_{i,j} \leq b_i, \quad \forall i\\ & x_{i,j} \in \{0, 1\}. \end{aligned}

模型1和模型2本質上是相同的, 因此當品牌數和商品數量非常大時直接求解模型2依然非常困難. 下面我們使用DW分解進行求解.

P_i = \{x_{i,j} \mid \sum_{j=1}^{n_i} x_{i,j} \leq b_i \}, i=1,2, \ldots, m. 注意到P_i是有界的, 我們用
v_{i,1}^{(k)}, v_{i,2}^{(k)}, \ldots, v_{i,p_i}^{(k)}, \quad k=1, 2, \ldots, p_i

代表P_i的頂點, 因此x_{i,j}可以被表示成如下形式:
x_{i,j} = \sum_{k=1}^{p_i}\lambda_{i,k}\cdot v_{i,j}^{(k)}, \quad \text{其中 } \sum_{k=1}^{p_i} \lambda_{i,k} = 1,\ \lambda_{i,k}\geq 0.
把它代入模型2中我們得到主問題的形式.

主問題
\begin{aligned} \max\ & \sum_{i=1}^m\sum_{j=1}^{n_i} \sum_{k=1}^{p_i}\lambda_{i,k} (p_{i,j}v_{i,j}^{(k)}) \\ \text{s.t. } & \sum_{i=1}^m \sum_{j=1}^{n_i} \sum_{k=1}^{p_i}\lambda_{i,k}(c_{i,j}v_{i,j}^{(k)}) \leq d \\ & \sum_{k=1}^{p_i} \lambda_{i,k} =1, \quad \forall i \\ & \lambda_{i,k} \geq 0, \quad \forall i, j, k. \end{aligned}

定義對偶變量yz_i, i=1,2,\ldots, m. 計算\lambda_{i,k}對應的reduced cost:
\alpha_i^{(k)} = \sum_{j=1}^{n_i} p_{i,j}v_{i,j}^{(k)} - \sum_{j=1}^{n_i}y\cdot c_{i,j}v_{i,j}^{(k)} - z_i.

注意: 主問題是最大化問題, 因此\alpha_i^{(k)}>0意味著可以提升主問題的目標函數值. 我們有:

  1. 子問題是最大化問題.
  2. 當所有為\alpha_i^{(k)} \leq 0時主問題達到最優.

子問題 - i
\begin{aligned} \min\ & \sum_{j=1}^{n_i}( p_{i,j}-yc_{i,j})x_{i,j} \\ \text{s.t. } & \sum_{j=1}^{n_i}x_{i,j} \leq b_i \\ & x_{i,j} \in \{0, 1\}. \end{aligned}

初始化

對任意的i=1, 2, \ldots, m, 定義向量:
v_i = (0, 0, \ldots, 0)^T \in \mathbb{R}^{b_i}.
顯然v_i是每個約束i的可行解, 即\sum_{j=1}^{b_i} v_{i,j} \leq b_i, \forall i. 我們用v_1, v_2, \ldots, v_m作為主問題初始化的頂點.

Remark. 前文的推導過程要求v_i是多面體的頂點, 但上面v_i的定義并不滿足此條件. 這么做可行的原因是任意可行解本身就是多面體頂點的凸組合.

求解

求解的基本步驟如下:

  1. 初始化主問題, 求解子問題的輸入參數y
  2. 求解m個子問題,分別計算\lambda_{i,k}對應的Reduced Cost \alpha_i^{(k)}. 如果\alpha_i^{(k)}>0, 則把對應的解v_i^{(k)}加入到主問題. (k可以理解為迭代的次數)
  3. 如果所有的\alpha_i^{(k)} < 0, 則停止迭代;否則迭代求解主問題和子問題直到滿足停止條件.

Python實現

主問題模型

# master_model.py

from ortools.linear_solver import pywraplp


class MasterModel(object):

    def __init__(self, p, v, c, d):
        """
        :param p: p[i][j]代表品牌i中商品j的預期收益
        :param v: v[i]代表第i個子問題的解
        :param c: c[i][j]代表品牌i中商品j的營銷成本
        :param d: scalar, 總預算
        """
        self._solver = pywraplp.Solver('MasterModel',
                                       pywraplp.Solver.GLOP_LINEAR_PROGRAMMING)
        self._p = p
        self._v = v
        self._c = c
        self._d = d
        self._la = None  # 決策變量lambda
        self._constraint_y = None  # 約束
        self._constraint_z = []  # 約束
        self._solution_la = None  # 計算結果

    def _init_decision_variables(self):
        self._la = [[]] * len(self._v)
        self._solution_la = [[]] * len(self._v)  # 初始化保存結果的變量
        for i in range(len(self._v)):
            self._la[i] = [[]] * len(self._v[i])
            self._solution_la[i] = [[]] * len(self._v[i])  # 初始化保存結果的變量
            for k in range(len(self._v[i])):
                self._la[i][k] = self._solver.NumVar(0, 1, 'la[%d][%d]' % (i, k))

    def _init_constraints(self):
        self._constraint_y = self._solver.Constraint(0, self._d)
        for i in range(len(self._v)):
            for k in range(len(self._v[i])):
                f = 0
                for j in range(len(self._v[i][k])):
                    f += self._c[i][j] * self._v[i][k][j]
                self._constraint_y.SetCoefficient(self._la[i][k], f)

        self._constraint_z = [None] * len(self._v)
        for i in range(len(self._v)):
            self._constraint_z[i] = self._solver.Constraint(1, 1)
            for k in range(len(self._la[i])):
                self._constraint_z[i].SetCoefficient(self._la[i][k], 1)

    def _init_objective(self):
        obj = self._solver.Objective()
        for i in range(len(self._v)):
            for k in range(len(self._v[i])):
                f = 0
                for j in range(len(self._v[i][k])):
                    f += self._p[i][j] * self._v[i][k][j]
                obj.SetCoefficient(self._la[i][k], f)
        obj.SetMaximization()

    def solve(self):
        self._init_decision_variables()
        self._init_constraints()
        self._init_objective()
        self._solver.Solve()
        # 保存計算結果
        for i in range(len(self._v)):
            for k in range(len(self._v[i])):
                self._solution_la[i][k] = self._la[i][k].solution_value()

    def get_solution_value(self):
        return self._solution_la

    def get_y(self):
        """ 獲取對偶變量y的值
        """
        return self._constraint_y.dual_value()

    def get_zi(self, i):
        """ 獲取對偶變量z[i]的值
        """
        return self._constraint_z[i].dual_value()

    def get_obj_value(self):
        res = 0
        for i in range(len(self._p)):
            for k in range(len(self._v[i])):
                for j in range(len(self._p[i])):
                    res += self._solution_la[i][k] * self._p[i][j] * self._v[i][k][j]
        return res

    def get_solution_x(self):
        """ 得到原問題的解.  x[i][j] = sum(la[i][k] * v[i][k][j]) over k.
        """

        x = [[]] * len(self._v)
        for i in range(len(self._v)):
            x[i] = [0] * len(self._v[i][0])

        for i in range(len(self._v)):
            for k in range(len(self._v[i])):
                for j in range(len(self._v[i][k])):
                    x[i][j] += self._solution_la[i][k] * self._v[i][k][j]
        return x

子問題模型

# sub_model.py

from ortools.linear_solver import pywraplp
import numpy as np


class SubModel(object):
    """ 子問題i
    """
    def __init__(self, pi, ci, y, bi):
        """ 下標i忽略
        :param pi: list, pi := p[i] = [p1, p2, ..., ]
        :param ci: list, ci := c[i] = [c1, c2, ..., ]
        :param y: scalar
        :param bi: scalar
        """
        self._solver = pywraplp.Solver('MasterModel',
                                       pywraplp.Solver.CBC_MIXED_INTEGER_PROGRAMMING)
        self._pi = pi
        self._ci = ci
        self._y = y
        self._bi = bi
        self._x = None  # 決策變量
        self._solution_x = None  # 計算結果

    def _init_decision_variables(self):
        self._x = [None] * len(self._pi)
        for j in range(len(self._pi)):
            self._x[j] = self._solver.IntVar(0, 1, 'x[%d]' % j)

    def _init_constraints(self):
        ct = self._solver.Constraint(0, self._bi)
        for j in range(len(self._pi)):
            ct.SetCoefficient(self._x[j], 1)

    def _init_objective(self):
        obj = self._solver.Objective()
        for j in range(len(self._pi)):
            obj.SetCoefficient(self._x[j], self._pi[j] - self._y * self._ci[j])
        obj.SetMaximization()

    def solve(self):
        self._init_decision_variables()
        self._init_constraints()
        self._init_objective()
        self._solver.Solve()
        self._solution_x = [s.solution_value() for s in self._x]

    def get_solution_x(self):
        return self._solution_x

    def get_obj_value(self):
        p = np.array(self._pi)
        c = np.array(self._ci)
        x = np.array(self._solution_x)
        return sum((p - self._y * c) * x)

DW分解的求解過程

# dw_proc.py

from master_model import MasterModel
from sub_model import SubModel


class DWProc(object):

    def __init__(self, p, c, d, b, max_iter=1000):
        """
        :param p: p[i][j]代表品牌i中商品j的預期收益
        :param c: c[i][j]代表品牌i中商品j的營銷成本
        :param d: 總營銷成本, int
        :param b: b[i]代表選中品牌i的商品數量限制
        """
        self._p = p
        self._c = c
        self._d = d
        self._b = b
        self._v = None  # 待初始化
        self._max_iter = max_iter
        self._iter_times = 0
        self._status = -1
        self._reduced_costs = [1] * len(self._p)
        self._solution_x = None  # 計算結果
        self._obj_value = 0  # 目標函數值

    def _stop_criteria_is_satisfied(self):
        """ 根據reduced cost判斷是否應該停止迭代.
        注意: 這是最大化問題, 因此所有子問題對應的reduced cost <= 0時停止.
        """
        st = [0] * len(self._reduced_costs)
        for i in range(len(self._reduced_costs)):
            if self._reduced_costs[i] < 1e-6:
                st[i] = 1
        if sum(st) == len(st):
            self._status = 0
            return True
        if self._iter_times >= self._max_iter:
            if self._status == -1:
                self._status = 1
            return True
        return False

    def _init_v(self):
        """ 初始化主問題的輸入
        """
        self._v = [[]] * len(self._p)
        for i in range(len(self._p)):
            self._v[i] = [[0] * len(self._p[i])]

    def _append_v(self, i, x):
        """ 把子問題i的解加入到主問題中

        :param x: 子問題i的解
        """
        self._v[i].append(x)

    def solve(self):
        # 初始化主問題并求解
        self._init_v()
        mp = MasterModel(self._p, self._v, self._c, self._d)
        mp.solve()
        self._iter_times += 1
        # 迭代求解主問題和子問題直到滿足停止條件
        while not self._stop_criteria_is_satisfied():
            # 求解子問題
            print("==== iter %d ====" % self._iter_times)
            for i in range(len(self._p)):
                # 求解子問題
                sm = SubModel(self._p[i], self._c[i], mp.get_y(), self._b[i])
                sm.solve()
                # 更新reduced cost
                self._reduced_costs[i] = sm.get_obj_value() - mp.get_zi(i)
                # 把子問題中滿足條件的解加入到主問題中
                if self._reduced_costs[i] > 0:
                    self._append_v(i, sm.get_solution_x())
                print(">>> Solve sub problem %d, reduced cost = %f" % (i, self._reduced_costs[i]))

            # 求解主問題
            mp = MasterModel(self._p, self._v, self._c, self._d)
            mp.solve()

            self._iter_times += 1

        self._solution_x = mp.get_solution_x()
        self._obj_value = mp.get_obj_value()
        status_str = {-1: "error", 0: "optimal", 1: "attain max iteration"}
        print(">>> Terminated. Status:", status_str[self._status])

    def print_info(self):
        print("==== Result Info  ====")
        print(">>> objective value =", self._obj_value)
        print(">>> Solution")
        sku_list = [[]] * len(self._solution_x)
        for i in range(len(self._solution_x)):
            sku_list[i] = [j for j in range(len(self._solution_x[i])) if self._solution_x[i][j] > 0]
        for i in range(len(self._solution_x)):
            print("brand %d, sku list:" % i, sku_list[i])

主函數

# main.py

from data import p, c, b, d  # instance data
from dw_proc import DWProc


if __name__ == '__main__':
    dw = DWProc(p, c, d, b)
    dw.solve()
    dw.print_info()

完整代碼

參考文獻


  1. George B. Dantzig; Philip Wolfe. Decomposition Principle for Linear Programs. Operations Research. Vol 8: 101–111, 1960. ?

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

推薦閱讀更多精彩內容