《浅谈保序回归问题》学习笔记
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 总结
看到这里请催我补了上一节。