《浅谈保序回归问题》学习笔记

1 引言

在我们的的实际生活中,保序回归问题常常出现于野鸡出题人出的联合省选题中。

2 保序回归问题

2.1 偏序关系

管它严不严谨,直接叫 \(\le\) 算了。

2.2 问题描述

给定一张 DAG,每个点有参数 \(y_i,w_i\),其中 \(w_i>0\)。要给每个点一个权值 \(f_i\),对于每条边 \((u,v)\)\(f_u\le f_v\)

  • \(L_p\) 问题:最小化 \(\sum w_i|f_i-y_i|^p\)
  • \(L_{\infty}\) 问题:最小化 \(\max w_i|f_i-y_i|\)

2.3 一些约定

将序列中 \(\le a\) 的变成 \(a\)\(\ge b\) 的变成 \(b\),叫做向集合 \(\{a,b\}\) 取整。

点集 \(U\)\(L_p\) 均值表示所有 \(f_i=x\) 的前提下,对应问题的式子取到最小值时的 \(x\)。注意可能不唯一。

3 特殊情形下的算法

搁这杂题选讲呢。

4 一般问题的算法

4.1 另一种思路——从整体二分谈起

相信大家都会。

4.2 新问题的构造

\(S=\{a,b\}\) 问题:\(L_p\) 问题加上限制 \(a\le f_i\le b\)

4.3 \(p=1\) 的情况

注意 \(L_1\) 均值不一定唯一,但一定形成一段区间。

引理 4.1:在 \(L_1\) 问题中,如果任意 \(y_i\) 均不在区间 \((a, b)\) 内,且存在一个最优解序列 \(z\) 满足其
元素 \(z_i\) 均不在区间 \((a, b)\) 内,若 \(z^S\)\(S\) 问题的一组最优解,那么一定存在 \(z\) 是原问题的一
组最优解且 \(z\) 可以通过向 \(S\) 取整得到 \(z^S\)

感受一下(并不严谨),如果任意 \(y_i\) 均不在 \((a,b)\) 内,肯定可以所有 \(z_i\) 都不在 \((a,b)\) 内,因为这里面的集体向左或向右平移 \(\epsilon\) 肯定不劣。同理 \(S\) 问题的解只包含 \(a\)\(b\)。而平移到 \(a\) 说明到左边更优,所以原问题中一定 \(\le a\)

那么就有了 \(L_1\) 问题的解法:将 \(y\) 排序去重变为 \(y'\),在上面整体二分(本质是在值域上),每次 \(a=y'_{mid},b=y'_{mid+1}\) 求最优解(是个最小权闭合子图),即可知道每个 \(f_i\)\(\le y'_{mid}\) 还是 \(\ge y'_{mid+1}\)

同时容易发现,答案里 \(f\) 都是 \(y\) 中出现过的数(其实引理 4.1 已经说明了这一点)。

4.4 \(1<p<\infty\) 的情况

引理 4.2:此时任意 \(U\)\(L_p\) 均值是唯一的。

证明大概把绝对值拆成分段函数然后求导。

引理 4.3:好像没讲人话。实际上就是 \((a,b)\) 满足不存在一个 \(U\)\(L_p\) 均值在里面,然后解 \(z^S\),同样也是向 \(S\) 取整。

证明跟 4.1 一样是感性理解。通过 4.2 同样可以将其中的点平移 \(\epsilon\)

至于 4.3 中的问题 \(S\) 同样也是最小权闭合子图,每个点的权值是选 \(b\) 和选 \(a\) 的差。

但是问题是此时答案并不是离散的(4.1 说明了 \(L_1\) 问题的答案离散,但是现在不满足在 \((y'_i,y'_{i+1})\) 中左右平移不变劣)。所以需要一个不包含所有 \(U\)\(L_p\) 均值的区间而不是 \((y'_{mid},y'_{mid+1})\) 这么简单。

不妨取 \((mid,mid+\epsilon)\)(此时就是真的在值域上二分)。直接做会有精度问题。注意到所有点权除以 \(\epsilon\) 答案不变,所以实际上点权设成 \(mid\) 处的导数即可。

4.5 网络流模型的建立

好像我之前就写了。

4.6 例题

求出偏序关系就全是板子,没啥意思。

顺便提一下引言中说的省选题。这题是 \(L_2\) 问题,但要求 \(f\) 也是整数。那么实际上比上面的分析过程还要简单,直接取区间 \((mid,mid+1)\) 即可,点权直接设成两者之差,不用求导。

下面给出代码。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> PII;
const int maxn=555555,mod=998244353;
#define PB push_back
#define MP make_pair
#define lson o<<1,l,mid
#define rson o<<1|1,mid+1,r
#define FOR(i,a,b) for(int i=(a);i<=(b);i++)
#define ROF(i,a,b) for(int i=(a);i>=(b);i--)
#define MEM(x,v) memset(x,v,sizeof(x))
template<typename T=int>
inline T read(){
	T x=0,f=0;char ch=getchar();
	while(ch<'0' || ch>'9') f|=ch=='-',ch=getchar();
	while(ch>='0' && ch<='9') x=x*10+ch-'0',ch=getchar();
	return f?-x:x;
}
int n,m,v[maxn],w[maxn],a[maxn],b[maxn],p[maxn],q[maxn],el,head[maxn],to[maxn],nxt[maxn],id[maxn];
bool va[maxn],vb[maxn];
ull c[maxn];
namespace linear_base{
	ull bit[66];
	void insert(ull x){
		ROF(i,63,0) if((x>>i)&1){
			if(!bit[i]) return bit[i]=x,void();
			x^=bit[i];
		}
	}
	bool check(ull x){
		ROF(i,63,0) if((x>>i)&1) x^=bit[i];
		return !x;
	}
	void clear(){
		MEM(bit,0);
	}
}
inline void add(int u,int v){
	to[++el]=v;nxt[el]=head[u];head[u]=el;
}
namespace max_flow{
	int el=1,head[maxn],cur[maxn],to[maxn],nxt[maxn],w[maxn],s,t,tot,q[maxn],h,r,dis[maxn];
	inline void add(int u,int v,int ww){
		to[++el]=v;nxt[el]=head[u];head[u]=el;w[el]=ww;
		to[++el]=u;nxt[el]=head[v];head[v]=el;w[el]=0;
	}
	bool bfs(){
		FOR(i,1,tot) dis[i]=-1,cur[i]=head[i];
		dis[s]=0;
		q[h=r=1]=s;
		while(h<=r){
			int u=q[h++];
			for(int i=head[u];i;i=nxt[i]){
				int v=to[i];
				if(w[i] && dis[v]==-1) q[++r]=v,dis[v]=dis[u]+1;
			}
		}
		return ~dis[t];
	}
	int dfs(int u,int res){
		if(u==t || !res) return res;
		int f,tot=0;
		for(int &i=cur[u];i;i=nxt[i]){
			int v=to[i];
			if(dis[v]==dis[u]+1 && (f=dfs(v,min(res,w[i])))){
				w[i]-=f;w[i^1]+=f;
				tot+=f;res-=f;
				if(!res) break;
			}
		}
		return tot;
	}
	int work(){
		int tot=0;
		while(bfs()) tot+=dfs(s,2e9);
		return tot;
	}
	void clear(){
		FOR(i,2,el) to[i]=nxt[i]=w[i]=0;
		FOR(i,1,tot) head[i]=0;
		s=t=tot=0;
		el=1;
	}
}
void solve(int l,int r,int L,int R){
	using max_flow::add;
	using max_flow::clear;
	using max_flow::work;
	using max_flow::tot;
	using max_flow::s;
	using max_flow::t;
	using max_flow::dis;
	if(l>r) return;
	if(L==R){
		FOR(i,l,r) w[p[i]]=L;
		return;
	}
	int mid=(L+R)>>1;
	FOR(i,l,r) id[p[i]]=i-l+1;
	FOR(uu,l,r){
		int u=p[uu];
		for(int i=head[u];i;i=nxt[i]){
			int v=to[i];
			if(id[v]) add(id[u],id[v],2e9);
		}
	}
	tot=t=r-l+3;
	s=r-l+2;
	FOR(uu,l,r){
		int u=p[uu],tmp=1ll*(v[u]-mid-1)*(v[u]-mid-1)-1ll*(v[u]-mid)*(v[u]-mid);
		if(tmp>0) add(id[u],t,tmp);
		if(tmp<0) add(s,id[u],-tmp);
	}
	work();
	int ql=l;
	FOR(uu,l,r){
		int u=p[uu];
		if(dis[id[u]]==-1) q[ql++]=p[uu];
	}
	int at=ql;
	FOR(uu,l,r){
		int u=p[uu];
		if(~dis[id[u]]) q[ql++]=p[uu];
	}
	FOR(i,l,r) id[p[i]=q[i]]=0;
	clear();
	solve(l,at-1,L,mid);
	solve(at,r,mid+1,R);
}
int main(){
	using namespace linear_base;
	n=read();m=read();
	FOR(i,1,n) c[i]=read<ull>();
	FOR(i,1,n) v[i]=read();
	FOR(i,1,m) va[a[i]=read()]=true;
	FOR(i,1,m) vb[b[i]=read()]=true;
	FOR(i,1,m){
		clear();
		FOR(j,1,m) if(i!=j) insert(c[a[j]]);
		FOR(j,1,n) if(!va[j] && !check(c[j])) add(a[i],j);
	}
	FOR(i,1,m){
		clear();
		FOR(j,1,m) if(i!=j) insert(c[b[j]]);
		FOR(j,1,n) if(!vb[j] && !check(c[j])) add(j,b[i]);
	}
	FOR(i,1,n) p[i]=i;
	solve(1,n,0,1e6);
	ll ans=0;
	FOR(i,1,n) ans+=1ll*(v[i]-w[i])*(v[i]-w[i]);
	printf("%lld\n",ans);
}

5 特殊偏序结构上的优化

5.1 树上问题

其实就是最小权闭合子图在树上随便做。

听说仙人掌也能做,好像确实,但凭啥要做这破玩意。

5.2 多维偏序

比如二维,发现整体二分时每一行剩下的是一个区间且左右端点单调不增。可以 dp,或许还能记后缀最小值或者上数据结构优化。

至于再高维可能还是很拉。

所以还是杂题选讲?

6 \(L_{\infty}\) 问题的算法与拓展

6.1 简单的二分法

学完别的保序回归不会做普及题了,学傻了?

直接二分答案,每个点有个取值范围,DAG 上传递一下取值范围就没了。

6.2 问题的拓展

可能论文作者也意识到不能普及题开一节

非常抱歉,这一部分咕了。

7 总结

看到这里请催我补了上一节。

posted @ 2021-05-25 20:20  ATS_nantf  阅读(275)  评论(2编辑  收藏  举报