Loading

保序回归问题小记

问题

\(n\) 个点,给出一张 DAG。

你需要给每个点设立权值 \(w_{1...n}\),满足对于每条边 \((u,v)\) 都有 \(w_u\le w_v\),求 \(\min\{\sum\limits_{i=1}^n b_i|w_i-a_i|^p\}\),其中 \(a_i,b_i,p\) 是给出的。

整体二分

考虑二分 \(mid\),把 DAG 划分为权值 \(\le mid\)\(>mid\) 两部分,显然两个部分都是连通块。

这里有个结论:若每个权值只能取 \(mid\)\(mid+1\),那么得到的最优方案和最终答案中把 \(\le mid\) 的改为 \(mid\)\(> mid\) 的改为 \(mid+1\)是一致的,具体证明详见 2018 年的高睿泉的集训队论文《浅谈保序回归问题》。

因此,我们只考虑每个点权值取 \(mid\) 还是 \(mid+1\)。我们钦定所有点都设为 \(mid\),设 \(d_i=b_i|(mid+1)-a_i|^p-b_i|mid-a_i|^p\),我们相当于在 DAG 中选取一个关于 \(d_i\) 的最小权闭合子图,然后把这些点设为 \(mid+1\)

考虑最小割求解最小权闭合子图,得出对应的两个点集 \(S,T\),此时 \(S\) 中的点权值为 \(mid+1\)\(T\) 中的为 \(mid\),分治下去处理即可。

例题

CF1615H Reindeer Games

模板题。

考虑保序回归问题,这里 \(p=1\)

给出一份实现。

点击查看代码
#include <bits/stdc++.h>
#define ll long long
#define ull unsigned ll
#define pir pair<ll,ll>
#define mkp make_pair
#define fi first
#define se second
#define pb push_back
using namespace std;
const ll maxn=3010, M=1e6, inf=1e17;
ll n,m,a[maxn],b[maxn];
ll ver[maxn];
vector<ll>to[maxn];
ll head[M], cur[M], tot;
struct edge{
	ll v,w,nxt;
} e[M];
void ins(ll u,ll v,ll w){
	e[++tot]=(edge){v,w,head[u]}; head[u]=tot;
	e[++tot]=(edge){u,0,head[v]}; head[v]=tot;
} ll v1[maxn], v2[maxn];
ll s, t, W[maxn];
void clr(){
	for(ll i=2;i<=tot;i++) head[e[i].v]=0;
	tot=1;
}
ll q[M], l, r, dis[M];
ll bfs(){
	for(ll i=1;i<=t;i++) dis[i]=-1;
	q[l=r=1]=s, dis[s]=0;
	while(l<=r){
		ll u=q[l++];
		for(ll i=head[u];i;i=e[i].nxt){
			ll v=e[i].v, w=e[i].w;
			if(w&&dis[v]==-1){
				dis[v]=dis[u]+1;
				q[++r]=v;
			}
		}
	} return dis[t]!=-1;
}
ll dfs(ll u,ll flow){
	if(u==t) return flow;
	ll used=0;
	for(ll i=cur[u];i;cur[u]=i=e[i].nxt){
		ll v=e[i].v, w=e[i].w;
		if(w&&dis[v]==dis[u]+1){
			ll tmp=dfs(v,min(flow-used,w));
			used+=tmp;
			e[i].w-=tmp, e[i^1].w+=tmp;
			if(used==flow) break;
		}
	} return used;
}
void mincut(){
	ll ret=0;
	while(bfs()){
		for(ll i=1;i<=t;i++) cur[i]=head[i];
		ret+=dfs(s,inf);
	}
} ll vis[maxn];
void Dfs(ll u){
	if(vis[u]) return; vis[u]=1;
	for(ll i=head[u];i;i=e[i].nxt)
		if(e[i].w) Dfs(e[i].v);
} ll inq[maxn];
void solve(ll l,ll r,ll kl,ll kr){
	if(kl==kr){
		for(ll i=l;i<=r;i++) b[ver[i]]=kl;
		return;
	}
	if(l>r) return; clr();
	for(ll i=l;i<=r;i++) inq[ver[i]]=1;
	s=n+1, t=s+1; ll mid=kl+kr>>1;
	for(ll i=l;i<=r;i++){
		ll u=ver[i]; W[u]=(a[u]<=mid? -1:1), vis[u]=0;
		if(W[u]>0) ins(s,u,W[u]);
		else ins(u,t,-W[u]);
		for(ll v:to[u])
			if(inq[v]) ins(u,v,inf);
	} mincut();
	vis[s]=vis[t]=0, Dfs(s); ll l1=0, l2=0;
	for(ll i=l;i<=r;i++){
		ll u=ver[i];
		if(vis[u]) v2[++l2]=u;
		else v1[++l1]=u;
	}
	for(ll i=0;i<l1;i++) ver[l+i]=v1[i+1];
	for(ll i=0;i<l2;i++) ver[l+l1+i]=v2[i+1];
	solve(l,l+l1-1,kl,mid);
	solve(l+l1,r,mid+1,kr);
}
int main(){
	scanf("%lld%lld",&n,&m);
	for(ll i=1;i<=n;i++) scanf("%lld",a+i), ver[i]=i;
	for(ll i=1;i<=m;i++){
		ll u,v; scanf("%lld%lld",&u,&v);
		to[u].pb(v);
	} solve(1,n,-1e15,1e15);
	for(ll i=1;i<=n;i++) printf("%lld ",b[i]);
	return 0;
}

Luogu6621 [省选联考 2020 A 卷] 魔法商店

显然,礼品集合构成一个线性拟阵。

对于两个基 \(A,C\),根据交换定理,考虑通过舍弃旧礼品,插入新礼品一步一步把 \(A\) 变成 \(C\),因此我们只需要考虑旧礼品和新礼品之间的偏序关系。

枚举 \(A\) 中的礼品,和一个新礼品,判断是否可以替换,若可以则建边,\(B\) 同理。

然后这是一个 \(p=2\) 的保序回归问题,套用模板即可。

点击查看代码
#include <bits/stdc++.h>
#define ll long long
#define ull unsigned ll
#define pir pair<ll,ll>
#define mkp make_pair
#define fi first
#define se second
#define pb push_back
using namespace std;
const ll maxn=2010, M=1e6, inf=1e17;
ll n,m,a[maxn],b[maxn];
ll ver[maxn];
vector<ll>to[maxn];
ll head[M], cur[M], tot;
struct edge{
	ll v,w,nxt;
} e[M];
void ins(ll u,ll v,ll w){
	e[++tot]=(edge){v,w,head[u]}; head[u]=tot;
	e[++tot]=(edge){u,0,head[v]}; head[v]=tot;
} ll v1[maxn], v2[maxn];
ll s, t, W[maxn];
void clr(){
	for(ll i=2;i<=tot;i++) head[e[i].v]=0;
	tot=1;
}
ll q[M], l, r, dis[M];
ll bfs(){
	for(ll i=1;i<=t;i++) dis[i]=-1;
	q[l=r=1]=s, dis[s]=0;
	while(l<=r){
		ll u=q[l++];
		for(ll i=head[u];i;i=e[i].nxt){
			ll v=e[i].v, w=e[i].w;
			if(w&&dis[v]==-1){
				dis[v]=dis[u]+1;
				q[++r]=v;
			}
		}
	} return dis[t]!=-1;
}
ll dfs(ll u,ll flow){
	if(u==t) return flow;
	ll used=0;
	for(ll i=cur[u];i;cur[u]=i=e[i].nxt){
		ll v=e[i].v, w=e[i].w;
		if(w&&dis[v]==dis[u]+1){
			ll tmp=dfs(v,min(flow-used,w));
			used+=tmp;
			e[i].w-=tmp, e[i^1].w+=tmp;
			if(used==flow) break;
		}
	} return used;
}
void mincut(){
	ll ret=0;
	while(bfs()){
		for(ll i=1;i<=t;i++) cur[i]=head[i];
		ret+=dfs(s,inf);
	}
} ll vis[maxn];
void Dfs(ll u){
	if(vis[u]) return; vis[u]=1;
	for(ll i=head[u];i;i=e[i].nxt)
		if(e[i].w) Dfs(e[i].v);
} ll inq[maxn];
void solve(ll l,ll r,ll kl,ll kr){
	if(kl==kr){
		for(ll i=l;i<=r;i++) b[ver[i]]=kl;
		return;
	}
	if(l>r) return; clr();
	for(ll i=l;i<=r;i++) inq[ver[i]]=1;
	s=n+1, t=s+1; ll mid=kl+kr>>1;
	for(ll i=l;i<=r;i++){
		ll u=ver[i]; W[u]=2*(a[u]-mid)-1, vis[u]=0;
		if(W[u]>0) ins(s,u,W[u]);
		else ins(u,t,-W[u]);
		for(ll v:to[u])
			if(inq[v]) ins(u,v,inf);
	} mincut();
	vis[s]=vis[t]=0, Dfs(s); ll l1=0, l2=0;
	for(ll i=l;i<=r;i++){
		ll u=ver[i]; inq[u]=0;
		if(vis[u]) v2[++l2]=u;
		else v1[++l1]=u;
	}
	for(ll i=0;i<l1;i++) ver[l+i]=v1[i+1];
	for(ll i=0;i<l2;i++) ver[l+l1+i]=v2[i+1];
	solve(l,l+l1-1,kl,mid);
	solve(l+l1,r,mid+1,kr);
} ll x[maxn], y[maxn], vx[maxn], vy[maxn], d[maxn]; ull ct[maxn];
int main(){
	scanf("%lld%lld",&n,&m);
	for(ll i=1;i<=n;i++) scanf("%llu",ct+i);
	for(ll i=1;i<=n;i++) scanf("%lld",a+i), ver[i]=i;
	for(ll i=1;i<=m;i++) scanf("%lld",x+i), vx[x[i]]=1;
	for(ll i=1;i<=m;i++) scanf("%lld",y+i), vy[y[i]]=1;
	for(ll i=1;i<=m;i++){ memset(d,0,sizeof d);
		for(ll k=1;k<=m;k++)
			if(k!=i){ ll val=ct[x[k]];
				for(ll l=63;~l;l--)
					if(val&(1ll<<l)){
						if(!d[l]) {d[l]=val; break;}
						val^=d[l];
					}
			}
		for(ll j=1;j<=n;j++){
			if(j==x[i]) continue;
			ll val=ct[j];
			for(ll l=63;~l;l--)
				if(val&(1ll<<l)){
					if(!d[l]) break;
					val^=d[l];
				}
			if(val) to[x[i]].pb(j);
		}
	}
	for(ll i=1;i<=m;i++){ memset(d,0,sizeof d);
		for(ll k=1;k<=m;k++)
			if(k!=i){ ll val=ct[y[k]];
				for(ll l=63;~l;l--)
					if(val&(1ll<<l)){
						if(!d[l]) {d[l]=val; break;}
						val^=d[l];
					}
			}
		for(ll j=1;j<=n;j++){
			if(j==y[i]) continue;
			ll val=ct[j];
			for(ll l=63;~l;l--)
				if(val&(1ll<<l)){
					if(!d[l]) break;
					val^=d[l];
				}
			if(val) to[j].pb(y[i]);
		}
	}
	solve(1,n,0,1e6); ll ret=0;
	for(ll i=1;i<=n;i++) ret+=(a[i]-b[i])*(a[i]-b[i]);
	printf("%lld",ret);
	return 0;
}
posted @ 2024-04-28 22:23  Sktn0089  阅读(8)  评论(0编辑  收藏  举报