《浅谈保序回归问题》学习笔记
保序回归问题
偏序关系
定义二元关系 \(\preceq\) 为集合 \(S\) 上的偏序关系,满足:
- 自反性:\(\forall x\in S,x\preceq x\);
- 反对称性:\(\forall x,y\in S,x\preceq y\land y\preceq x\Rightarrow x=y\);
- 传递性:\(\forall x,y,z\in S,x\preceq y\land y\preceq z\Rightarrow x\preceq z\)。
\(L_p\) 问题
问题: 给定 DAG \(G=(V=\{v_i\}_{i=1}^n,E)\)、代价函数 \(\{(y_i,w_i)\}_{i=1}^n\)。定义偏序关系 \(v_i\preceq v_j\) 当且仅当 \(G\) 中存在 \(v_i\) 到 \(v_j\) 的路径。
求出
序列 \(z\) 向集合 \(S=\{a,b\}\) 取整
\(\{z_i\}_{i=1}^n\gets \{\min\{b,\max\{a,z_i\}\}\}_{i=1}^n\)
点集 \(U\) 的 \(L_p\) 均值
使得 \(\sum_{v_i\in U}w_i|y_i-k|^p(p<\infty)\) 或 \(\max_{v_i\in U}w_i|y_i-k|(p=\infty)\) 取到最小值的 \(k\)。
一般问题的解法
\(L_p\) 的 \(S=\{a,b\}(a<b)\) 问题
在 \(L_p\) 问题中增加 \(\forall 1\leq i\leq n,a\leq f_i\leq b\) 的限制。
\(L_1\) 问题解法
引理: 若 \(\forall 1\leq i\leq n,y_i\notin (a,b)\) 且存在最优解序列 \(\{z_i\}_{i=1}^n,s.t.\forall 1\leq i\leq n,z_i\notin (a,b)\),那么对于 \(S=\{a,b\}\) 问题的最优解 \(z^S\),都存在一个原问题的最优解 \(z\),满足 \(z\) 向 \(S\) 取整后得到 \(z^S\)。
对于 \(L_1\) 问题的最优解 \(z\),\(z\) 中的元素一定在 \(y\) 构成的集合 \(\{Y_i\}_{i=1}^k(\forall 1\leq i<k,Y_i<Y_{i+1})\) 中。于是可以进行整体二分:当二分到 \(Y_{[l,r]}\) 时,求出 \(S=\{Y_{mid},Y_{mid+1}\}\) 问题的最优解,然后通过这一组解将此区间内的节点重新划分至 \(Y_{[l,mid]}\) 和 \(Y_{[mid+1,r]}\) 递归求解。
\(L_p(1<p<\infty)\) 问题解法
引理: \(\forall 1<p<\infty,U\subset V\),\(U\) 的 \(L_p\) 均值是唯一的。
引理: 若 \(\forall U\subset V\),\(U\) 的 \(L_p\) 均值不在 \((a,b)(a<b)\) 中且存在最优解 \(\{z_i\}_{i=1}^n,s.t.\forall 1\leq i\leq n,z_i\notin (a,b)\)。那么对于代价函数为 \((y',w')\) 的 \(L_1\) 问题的最优解 \(\tilde{z}\),存在原问题的最优解 \(\{z_i\}_{i=1}^n,s.t.\forall 1\leq i\leq n,z_i\leq a\Leftrightarrow \tilde{z}_i=0\)。其中:
类似于 \(L_1\) 问题,可以进行在实数上的整体二分:当二分到 \([l,r]\) 时,选定一个极小的正实数 \(\epsilon\),那么 \((a,b)=(mid,mid+\epsilon)\) 时满足上述条件,求出 \(\tilde{z}\),进行递归求解(不影响结果地,可以将 \(w'\) 同时除以 \(\epsilon\),变为原表达式在 \(mid\) 处的导数)。
\(L_1\) 的 \(S\) 问题解法
对于一般 DAG,可以抽象为最小权闭合子图问题,用网络流解决。
对于树/仙人掌/多维偏序,可以根据特殊性质进行 dp 求解。
\(L_\infty\) 问题的解法与扩展
二分法
二分答案+DAG 上 dp。
问题拓展
问题: 在满足 \(L_\infty\) 的限制下,对于每个 \(1\leq k\leq n\) 求出 \(\min\{\max_{i=1}^kw_i|f_i-y_i|\}\)。另外,增加条件 \(\forall 1\leq i<j\leq n,v_i\preceq v_j\)。
定义 \(err(v_i,r)=w_i|r-y_i|\)、\(mean(v_i,v_j)=\frac{w_iy_i+w_jy_j}{w_i+w_j}\)。
当 \(v_i\preceq v_j\land y_i>y_j\) 时,\(mean\_err(v_i,v_j)=\max\{err(v_i,mean(v_i,v_j)),err(v_j,mean(v_i,v_j))\}\);否则 \(mean\_err(v_i,v_j)=0\)。
引理: 对于任意 \(L_\infty\) 问题,整张图的答案为 \(\max\{mean\_err(v_i,v_j\}\)。
设 \(pre(v_i)=\max\{mean\_err(v_j,v_i)|v_j\preceq v_i\}\)。
那么对于任意 DAG 可以 \(O(n^2)\) 求出 \(pre\),进而求出答案。对于新增加的条件,可以通过维护凸包以做到 \(O(n\log n)\)。
例题
[省选联考 2020 A 卷] 魔法商店
简要题意
给定一个非负整数集合,每个元素有一个权值,你可以修改每个元素的权值各一次,代价为修改前后权值差的平方。指定该集合的两组不同线性基 \(A,B\),要求 \(A\) 为所有线性基中权值和最小的,\(B\) 为最大的。求出最小代价。
题解
要求等价于 \(A\) 中每个元素的权值都要小于等于能替换该元素的其他元素的权值,\(B\) 反之。建立出偏序关系然后跑 \(L_2\) 问题即可。
code
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<queue>
#define inf 0x3f3f3f3f
#define ull unsigned long long
#define N 1005
#define M 70
namespace XB{
#define W 64
ull d[W];
inline void cl(){
for(int i=0;i<W;i++)
d[i]=0;
}
inline void ins(ull x){
for(int i=W-1;i>=0;i--)
if((x>>i)&1){
if(!d[i]){
d[i]=x;
break;
}
x^=d[i];
}
}
inline bool chk(ull x){
for(int i=W-1;i>=0;i--)
if((x>>i)&1)
x^=d[i];
return x>0;
}
}
namespace MF{
int n,s,t;
int hd[N],_hd;
struct edge{
int v,f,nxt;
}e[N*M<<1];
inline void addedge(int u,int v,int f){
e[++_hd]=(edge){v,f,hd[u]};
hd[u]=_hd;
e[++_hd]=(edge){u,0,hd[v]};
hd[v]=_hd;
}
inline void init(int n_,int s_,int t_){
for(int i=1;i<=n;i++)
hd[i]=0;
_hd=1;
n=n_,s=s_,t=t_;
}
std::queue<int> q;
int cur[N],dis[N];
inline bool bfs(){
for(int i=1;i<=n;i++)
cur[i]=hd[i];
for(int i=1;i<=n;i++)
dis[i]=inf;
dis[s]=0;
q.push(s);
while(q.size()){
int u=q.front();
q.pop();
for(int i=hd[u];i;i=e[i].nxt){
int v=e[i].v,f=e[i].f;
if(f&&dis[v]>dis[u]+1){
dis[v]=dis[u]+1;
q.push(v);
}
}
}
return dis[t]<inf;
}
inline int dfs(int u,int lmt){
if(u==t||!lmt)
return lmt;
int res=0;
for(int i=cur[u];i;i=e[i].nxt){
cur[u]=i;
int v=e[i].v,f=e[i].f;
if(dis[v]!=dis[u]+1)
continue;
f=dfs(v,std::min(lmt,f));
e[i].f-=f,e[i^1].f+=f;
lmt-=f,res+=f;
if(!lmt)
break;
}
return res;
}
inline void sol(){
while(bfs())
dfs(s,inf);
}
}
int n,m,y[N],a[M],b[M];
ull c[N];
std::vector<int> E[N];
int f[N],p[N],id[N],q[N];
inline void sol(int L,int R,int l,int r){
if(l>r)
return;
if(L==R){
for(int i=l;i<=r;i++)
f[p[i]]=L;
return;
}
int mid=(L+R)>>1;
MF::init(r-l+3,r-l+2,r-l+3);
for(int i=l;i<=r;i++)
id[p[i]]=i-l+1;
for(int i=l;i<=r;i++){
int u=p[i],w=2*(y[u]-mid)-1;
if(w>0)
MF::addedge(MF::s,id[u],w);
else
MF::addedge(id[u],MF::t,-w);
for(auto v:E[u])
if(id[v])
MF::addedge(id[u],id[v],inf);
}
MF::sol();
int pm=l-1;
for(int i=l;i<=r;i++)
if(MF::dis[id[p[i]]]==inf)
q[++pm]=p[i];
int tmp=pm;
for(int i=l;i<=r;i++)
if(MF::dis[id[p[i]]]<inf)
q[++tmp]=p[i];
for(int i=l;i<=r;i++)
p[i]=q[i];
for(int i=l;i<=r;i++)
id[p[i]]=0;
sol(L,mid,l,pm);
sol(mid+1,R,pm+1,r);
}
ull ans;
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
scanf("%llu",&c[i]);
for(int i=1;i<=n;i++)
scanf("%d",&y[i]);
for(int i=1;i<=m;i++)
scanf("%d",&a[i]);
for(int i=1;i<=m;i++)
scanf("%d",&b[i]);
for(int i=1;i<=m;i++){
XB::cl();
for(int j=1;j<=m;j++)
if(j!=i)
XB::ins(c[a[j]]);
for(int j=1;j<=n;j++)
if(j!=a[i]&&XB::chk(c[j]))
E[a[i]].push_back(j);
}
for(int i=1;i<=m;i++){
XB::cl();
for(int j=1;j<=m;j++)
if(j!=i)
XB::ins(c[b[j]]);
for(int j=1;j<=n;j++)
if(j!=b[i]&&XB::chk(c[j]))
E[j].push_back(b[i]);
}
for(int i=1;i<=n;i++)
p[i]=i;
sol(0,(int)1e6,1,n);
for(int i=1;i<=n;i++)
ans+=1ull*(f[i]-y[i])*(f[i]-y[i]);
printf("%llu\n",ans);
}