零、引言

昨天晚上,我看这篇论文,竟然看到头疼……

壹、保序回归

1.偏序关系

保序回归例子 浅谈保序回归问题_保序回归例子

如果不懂请上度娘搜,而且不懂也可以直接理解成实数的比较

2.问题描述

给出正整数 保序回归例子 浅谈保序回归问题_贪心算法_02 和代价函数 保序回归例子 浅谈保序回归问题_保序回归例子_03 满足 保序回归例子 浅谈保序回归问题_偏序_04 。给出定义在 保序回归例子 浅谈保序回归问题_偏序_05 上的非严格偏序关系 保序回归例子 浅谈保序回归问题_保序回归例子

求实数序列 保序回归例子 浅谈保序回归问题_偏序_07 满足 保序回归例子 浅谈保序回归问题_取值_08保序回归例子 浅谈保序回归问题_贪心算法_09,最小化回归代价
保序回归例子 浅谈保序回归问题_算法_10

贰、特殊情形下的算法

1.一种贪心算法

当偏序关系为简单的链即 保序回归例子 浅谈保序回归问题_保序回归例子_11

首先,定义 保序回归例子 浅谈保序回归问题_取值_12 。再定义 保序回归例子 浅谈保序回归问题_保序回归例子_13 为使得 保序回归例子 浅谈保序回归问题_保序回归例子_14 最小的 保序回归例子 浅谈保序回归问题_偏序_15,即 保序回归例子 浅谈保序回归问题_算法_16

1.1.理论推导

引理1保序回归例子 浅谈保序回归问题_保序回归例子_14 的导数(忽略 保序回归例子 浅谈保序回归问题_算法_18 处的奇点)单增,即 保序回归例子 浅谈保序回归问题_保序回归例子_14

引理二保序回归例子 浅谈保序回归问题_算法_20,即 保序回归例子 浅谈保序回归问题_保序回归例子_13 为导数 保序回归例子 浅谈保序回归问题_贪心算法_22 的零点。在 保序回归例子 浅谈保序回归问题_取值_23 时零点只有一个;在 保序回归例子 浅谈保序回归问题_算法_24 时零点有多个,不妨令 保序回归例子 浅谈保序回归问题_保序回归例子_13

定理一:若 保序回归例子 浅谈保序回归问题_偏序_26,则 保序回归例子 浅谈保序回归问题_取值_27

证明:考察导数即可。由 引理一引理二保序回归例子 浅谈保序回归问题_保序回归例子_28保序回归例子 浅谈保序回归问题_偏序_29 导数为负,故 保序回归例子 浅谈保序回归问题_算法_30,同理可证 保序回归例子 浅谈保序回归问题_保序回归例子_31

证明了 定理一,其实问题已经与 保序回归例子 浅谈保序回归问题_算法_24

推论一:对于集族 保序回归例子 浅谈保序回归问题_贪心算法_33,有 保序回归例子 浅谈保序回归问题_贪心算法_34

定理二:若最优解的 保序回归例子 浅谈保序回归问题_偏序_35 的某个极长等值段为 保序回归例子 浅谈保序回归问题_偏序_36,则 保序回归例子 浅谈保序回归问题_贪心算法_37

证明:否则可以给 保序回归例子 浅谈保序回归问题_贪心算法_38保序回归例子 浅谈保序回归问题_取值_39 统一增加充分小的偏移量 保序回归例子 浅谈保序回归问题_算法_40 使其逼近 保序回归例子 浅谈保序回归问题_保序回归例子_41 。由 引理一 知,这是更优的。

定理三:原问题最优解为 保序回归例子 浅谈保序回归问题_算法_42,当且仅当 保序回归例子 浅谈保序回归问题_保序回归例子_43 。——注意 保序回归例子 浅谈保序回归问题_保序回归例子_44

证明:必要性毋庸置疑,否则前后可以各自取到最优点 保序回归例子 浅谈保序回归问题_保序回归例子_44 。下证充分性。若实际上存在 保序回归例子 浅谈保序回归问题_取值_46,令 保序回归例子 浅谈保序回归问题_取值_47 为满足条件的最小 保序回归例子 浅谈保序回归问题_保序回归例子_48 。那么 保序回归例子 浅谈保序回归问题_偏序_49 一定由若干等值段构成,且每段的值都是 保序回归例子 浅谈保序回归问题_保序回归例子_44,根据 推论一 可知 保序回归例子 浅谈保序回归问题_算法_51 。根据限制条件,保序回归例子 浅谈保序回归问题_取值_52 就是第一个等值段,即 保序回归例子 浅谈保序回归问题_取值_53,又由 保序回归例子 浅谈保序回归问题_取值_47 的定义 保序回归例子 浅谈保序回归问题_贪心算法_55,故其不符合题目要求,矛盾。

定理四:若 保序回归例子 浅谈保序回归问题_取值_56保序回归例子 浅谈保序回归问题_贪心算法_57 都满足 定理三 的判定条件,且 保序回归例子 浅谈保序回归问题_偏序_58,则 保序回归例子 浅谈保序回归问题_偏序_36 满足 定理三 判定条件。

证明:分割点 保序回归例子 浅谈保序回归问题_偏序_60 时,右侧加上了较小的 保序回归例子 浅谈保序回归问题_取值_61,仍成立;分割点 保序回归例子 浅谈保序回归问题_贪心算法_62 时,左侧加上了较大的 保序回归例子 浅谈保序回归问题_算法_63,仍成立。

1.2.算法流程

维护单调栈,存储每一个目前得到的 保序回归例子 浅谈保序回归问题_偏序_35 等值段,以及这些等值段的 保序回归例子 浅谈保序回归问题_保序回归例子_44

新加入一个数时,首先将其自己视为单独的 保序回归例子 浅谈保序回归问题_偏序_35 等值段,放入栈中。重复检查栈顶两个元素:若序列中靠前的那个的 保序回归例子 浅谈保序回归问题_保序回归例子_44 大于靠后者的 保序回归例子 浅谈保序回归问题_保序回归例子_44,根据 定理四 必然会合并成大等值段,合并即可。

时间复杂度是 保序回归例子 浅谈保序回归问题_取值_69 次计算 保序回归例子 浅谈保序回归问题_保序回归例子_44 。当 保序回归例子 浅谈保序回归问题_算法_24 时,用 保序回归例子 浅谈保序回归问题_算法_72 做到均摊 保序回归例子 浅谈保序回归问题_偏序_73,当 保序回归例子 浅谈保序回归问题_贪心算法_02 为偶数时使用二分等方式求导数零点,是 保序回归例子 浅谈保序回归问题_保序回归例子_75保序回归例子 浅谈保序回归问题_贪心算法_76

2.维护折线算法

保序回归例子 浅谈保序回归问题_偏序_77 上被称为 保序回归例子 浅谈保序回归问题_贪心算法_78,比较常见,即维护类似绝对值函数的图像。

只适用于树形(递归子问题后合并)的 保序回归例子 浅谈保序回归问题_算法_24

叁、一般问题的算法

1.整体二分

这又是个让人摸不着头脑的巧妙的 保序回归例子 浅谈保序回归问题_贪心算法_80 。为了方便叙述,定义 保序回归例子 浅谈保序回归问题_算法_81保序回归例子 浅谈保序回归问题_取值_82 取整的结果是:若 保序回归例子 浅谈保序回归问题_取值_83 则结果为 保序回归例子 浅谈保序回归问题_偏序_84,否则结果为 保序回归例子 浅谈保序回归问题_贪心算法_85

我们构造一个新问题,叫做 保序回归例子 浅谈保序回归问题_取值_86 型问题,表示额外添加限制 保序回归例子 浅谈保序回归问题_贪心算法_87

定理五:若 保序回归例子 浅谈保序回归问题_算法_88,且存在最优解 保序回归例子 浅谈保序回归问题_算法_89 使得 保序回归例子 浅谈保序回归问题_算法_90,则 保序回归例子 浅谈保序回归问题_取值_86 型问题的最优解是将 保序回归例子 浅谈保序回归问题_偏序_35保序回归例子 浅谈保序回归问题_取值_82 取整后的序列 保序回归例子 浅谈保序回归问题_贪心算法_94

证明:类似 定理二,极大等值连通块的 保序回归例子 浅谈保序回归问题_偏序_35 应当取到 保序回归例子 浅谈保序回归问题_保序回归例子_44 。那么 保序回归例子 浅谈保序回归问题_取值_86 型问题当然要保持靠近 保序回归例子 浅谈保序回归问题_保序回归例子_44 的性质,即向 保序回归例子 浅谈保序回归问题_取值_82 取整呗。显然方案仍然是合法的。2

所以,我们求解 保序回归例子 浅谈保序回归问题_取值_86 型问题后,看看 保序回归例子 浅谈保序回归问题_保序回归例子_101 还是 保序回归例子 浅谈保序回归问题_偏序_102 就知道了原问题中 保序回归例子 浅谈保序回归问题_取值_103 还是 保序回归例子 浅谈保序回归问题_算法_104,实现了二分的目标。

保序回归例子 浅谈保序回归问题_算法_24 时,由 定理二保序回归例子 浅谈保序回归问题_取值_106,即存在最优解 保序回归例子 浅谈保序回归问题_保序回归例子_107 。设 保序回归例子 浅谈保序回归问题_保序回归例子_108 已排序,求解 保序回归例子 浅谈保序回归问题_保序回归例子_109

保序回归例子 浅谈保序回归问题_取值_23 时,解可能在实数范围内,故只能在实数域上二分,并且采用 保序回归例子 浅谈保序回归问题_偏序_111

2.网络流模型

由于 保序回归例子 浅谈保序回归问题_偏序_35保序回归例子 浅谈保序回归问题_取值_82 取整后,保序回归例子 浅谈保序回归问题_偏序_114,所以决策只有两种,可以用最大权闭合子图模型解决,偏序关系都可以变成 保序回归例子 浅谈保序回归问题_算法_115 容量的边。当 保序回归例子 浅谈保序回归问题_取值_23 时,两种决策的价值差太小,精度误差过大。需要另辟蹊径:价值只在物品上,将一个物品选 保序回归例子 浅谈保序回归问题_贪心算法_117 和选 保序回归例子 浅谈保序回归问题_保序回归例子_118 的价值都除以 保序回归例子 浅谈保序回归问题_算法_40,选择方案不变。而建边时,权值是 保序回归例子 浅谈保序回归问题_偏序_120,除以 保序回归例子 浅谈保序回归问题_算法_40

保序回归例子 浅谈保序回归问题_算法_122 的奇点上,由于 保序回归例子 浅谈保序回归问题_算法_40 从正方向逼近 保序回归例子 浅谈保序回归问题_取值_124,导数也是有定义的。

3.整数解

类似 定理五,若规定 保序回归例子 浅谈保序回归问题_贪心算法_125,则考虑 保序回归例子 浅谈保序回归问题_贪心算法_126 型问题,即可知原最优解 保序回归例子 浅谈保序回归问题_取值_103

证明过程与 定理五 无异:靠近 保序回归例子 浅谈保序回归问题_保序回归例子_44

肆、代码实现

一般来说,可以直接将 保序回归例子 浅谈保序回归问题_取值_08 直接建成边 保序回归例子 浅谈保序回归问题_取值_130

当我们需要对点集 保序回归例子 浅谈保序回归问题_算法_16 进行整体二分时,任取 保序回归例子 浅谈保序回归问题_保序回归例子_132,则 保序回归例子 浅谈保序回归问题_贪心算法_133 在有向图上的任意路径上的所有点都在 保序回归例子 浅谈保序回归问题_算法_16

有向图中的边,直接变为容量 保序回归例子 浅谈保序回归问题_算法_115 的边,表示 保序回归例子 浅谈保序回归问题_取值_136保序回归例子 浅谈保序回归问题_贪心算法_137 。最小割中的 保序回归例子 浅谈保序回归问题_算法_16 为选择 保序回归例子 浅谈保序回归问题_贪心算法_117 的,所以 保序回归例子 浅谈保序回归问题_算法_140 的容量是 保序回归例子 浅谈保序回归问题_取值_141 。——若该值为负,则改为 保序回归例子 浅谈保序回归问题_偏序_142

例题:限制 保序回归例子 浅谈保序回归问题_贪心算法_125,核心代码如下。

# define _go(i,x) for(int i=head[x]; ~i; i=e[i].nxt)
const int MAXN = 1003, MAXM = 200005;
const int INF = 0x3fffffff;
namespace Network{
	struct Edge{ int to, nxt, val; };
	Edge e[MAXM]; int head[MAXN], cntEdge;
	void _addEdge(int a, int b, int c){
		e[cntEdge].to = b, e[cntEdge].nxt = head[a];
		e[cntEdge].val = c, head[a] = cntEdge ++;
	}
	void addEdge(int a, int b, int c = 0){
		_addEdge(a,b,c), _addEdge(b,a,0);
	}
	void clear(const int &n){
		cntEdge = 0, memset(head+1,-1,n<<2);
	}
	int dis[MAXN], cur[MAXN];
	# define _PARAM_ const int &source,\
		const int &sink, const int &n
	bool bfs(_PARAM_){
		static int que[MAXN];
		memset(dis+1,-1,n<<2); // clear
		dis[source] = 0, que[1] = source;
		for(int x,*fro=que,*bac=que+1; fro!=bac; )
			_go(i,x=*(++fro)) if(e[i].val)
				if(!(~dis[e[i].to])){
					dis[e[i].to] = dis[x]+1;
					++ bac, *bac = e[i].to;
				}
		return dis[sink] != -1;
	}
	int dfs(int x, int inFlow, const int &sink){
		int sum = 0; if(x == sink) return inFlow;
		for(int &i=cur[x]; ~i; i=e[i].nxt)
			if(e[i].val && dis[e[i].to] == dis[x]+1){
				int d = dfs(e[i].to,std::min(
					inFlow-sum,e[i].val),sink);
				e[i].val -= d, e[i^1].val += d;
				if((sum += d) == inFlow) return sum;
			}
		dis[x] = -1; return sum;
	}
	int dinic(_PARAM_){
		int res = 0;
		while(bfs(source,sink,n)){
			memcpy(cur+1,head+1,n<<2);
			res += dfs(source,INF,sink);
		}
		return res;
	}
	bool is_inS(const int &x){ return ~dis[x]; }
}

struct Edge{ int to, nxt; };
Edge e[MAXM]; int head[MAXN], cntEdge;
void addEdge(int a, int b){
	e[cntEdge].to = b, e[cntEdge].nxt = head[a];
	head[a] = cntEdge ++;
}

int v[MAXN], ans[MAXN], haxi[MAXN];
void solve(int *l, int *r, int ql, int qr){
	if(l == r || ql == qr){
		for(int *i=l; i!=r; ++i) ans[*i] = ql;
		return ; // answer found
	}
	const int mid = (ql+qr)>>1;
	for(int *i=l,p=1; i!=r; ++i,++p) haxi[*i] = p;
	const int source = int(r-l)+1, sink = source+1;
	Network::clear(sink); // recalc
	for(int *i=l,p=1; i!=r; ++i,++p){
		int w = (mid-v[*i])<<1|1; // if choose (mid+1)
		if(w > 0) Network::addEdge(source,p,w);
		else if(w) Network::addEdge(p,sink,-w);
		_go(j,*i) if(haxi[e[j].to]) // in the range
			Network::addEdge(p,haxi[e[j].to],INF);
	}
	Network::dinic(source,sink,sink);
	for(int *i=l; i!=r; ++i) haxi[*i] = 0;
	static int _tmp[MAXN]; // auxiliary
	std::copy(l,r,_tmp); // backup
	int *pl = l, *pr = r; ///< refill
	for(int *i=_tmp,p=1; pl!=pr; ++i,++p)
		if(Network::is_inS(p)) *pl = *i, ++ pl;
		else -- pr, *pr = *i; // choose mid+1
	solve(l,pl,ql,mid), solve(pr,r,mid+1,qr);
}

  1. “引理” 的意思就是,过于显然而略去证明,只引述一下就差不多得了。 ↩︎
  2. 论文中,这里用了不少的篇幅,我便猜我的证明多有不严谨之处,希望读者能够简略指出。虽说修锅的可能性不大,顶多让它变为 “参见原论文”。 ↩︎