据说是一道论文题……
论文:2018集训队论文高睿泉《浅谈保序回归问题》
保序回归问题:
有一个正整数 p p p,给出一个有向图,点 i i i有权值 ( w i , y i ) (w_i,y_i) (wi,yi),需要调整 y i y_i yi的值使得 y i y_i yi满足有向无环图的偏序关系。调整的代价为前后 y i y_i yi的差的 p p p次方乘 w i w_i wi,求最小的代价。
形式化地说:给每个点赋一个新的权值 f i f_i fi,使得每条边 ( u , v ) ∈ E (u,v)in E (u,v)∈E满足 f u ≤ f v f_uleq f_v fu≤fv,求 ∑ w i ∣ f i − y i ∣ p sum w_i|f_i-y_i|^p ∑wi∣fi−yi∣p的最小值。
这样的问题记作 L p L_p Lp
其实这种问题之前已经做过一次了:jzoj6734. 【2020.06.18省选模拟】T2 航行
没错就是比赛的前天做到的题
套路做法:整体二分,强制每个权值只能选择 m i d mid mid或 m i d + 1 mid+1 mid+1,跑最大(小)权闭合子图,选 m i d mid mid的点最终的权值 ≤ m i d leq mid ≤mid,选 m i d + 1 mid+1 mid+1的点最终的权值 > m i d >mid >mid,分开来递归求解。
之前做的那一道题的限制关系比较优美,所以可以DP处理。
最大(小)权闭合子图:
“闭合子图”就是某个点集 V V V,满足对于任意 u ∈ V u in V u∈V,对于任意 ( u , v ) ∈ E (u,v)in E (u,v)∈E都有 e ∈ V ein V e∈V。用人话说就是从 V V V中的每个点开始遍历,
能够遍历到的每个点都在 V V V中。每个点上有个权值 w i w_i wi,要选出一个闭合子图使得点权最大。
一般套路:网络流,建个新图。对于每个点 i i i,如果 w i > 0 w_i>0 wi>0就连边 ( S , i , w i ) (S,i,w_i) (S,i,wi),如果 w i < 0 w_i<0 wi<0就连边 ( i , T , − w i ) (i,T,-w_i) (i,T,−wi)。原图中的每一条边都在新图中对应地连,容量无穷。
答案为 ∑ w i > 0 w i − 最 小 割 sum_{w_i>0} w_i-最小割 ∑wi>0wi−最小割
理解:如果不考虑限制,则贪心地选所有正权点是最优的。加入限制,对于一个点 u u u,如果它不选,则所有 v v v,满足从 v v v开始遍历可以走到 u u u,这些 v v v都不能选。显然所有 u u u满足这个条件是充分必要条件。
放在图中来看:割掉边 ( u , T ) (u,T) (u,T),即选 v v v,或者割掉边 ( S , v ) (S,v) (S,v),即不选 v v v。
具体来说,求解最大(小)权闭合子图的时候,如下处理:
将权值选 m i d mid mid记作“不选”,将权值选 m i d + 1 mid+1 mid+1记作“选”。
对于点 i i i,它的权值设为 − ( w ( i , m i d + 1 ) − w ( i , m i d ) ) -(w(i,mid+1)-w(i,mid)) −(w(i,mid+1)−w(i,mid))( w ( i , f ) w(i,f) w(i,f)为 y i y_i yi变为 f f f时的代价)。为什么这么设,因为本该跑最小权闭合子图,取个负号就变成最大权闭合子图了。当然也可以不这样设。
如果某个点“选”,那么它能遍历到的所有点都必须“选”。由此得出限制关系,建图。
跑最大权闭合子图。
答案即 ∑ w ( i , m i d ) − 最 大 权 sum w(i,mid)-最大权 ∑w(i,mid)−最大权
具体方案看 S S S集或 T T T集即可。
这题的正解大概可以分为两个部分:求出限制关系和求解保序回归问题。
后者上面已经讨论过了,就讲述一下前者。
只考虑 A A A带来的限制, B B B同理。
显然一个礼品集合就是一个线性基。假如有另一个线性基 C C C,它可以通过从 A A A开始,依次替换线性基中的向量得到,并且保证替换的过程中一直都是线性基。
表示不会证。(具体证明好像要用到拟阵之类的)。
假设知道了上面的结论是对的,那么显然我们只需考虑 A A A被替换了一个元素的情形。于是,对于每个数 c i c_i ci,找到 c i c_i ci能替换哪些元素,钦定它们都要小于等于 c i c_i ci。
不难发现 c i c_i ci能替换哪些元素,等价于 c i c_i ci能被表示成 A A A中的哪些元素异或起来。
这应该是线性基的一个基本操作吧。建线性基的时候,用个bitset
来表示线性基中的某个数是原来的哪几个数异或过来。查询的时候,将异或的线性基中的几个数的bitset
异或起来。详见代码。
这题 m m m比较小,直接开整型压位即可。
(有更优美的操作请路过的大佬指教)
using namespace std;
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <climits>
#define N 1005
#define M 64
#define ll long long
#define ull unsigned long long
#define INF LLONG_MAX
int n,m;
ull c[N];
int v[N],y[N];
int a[M],b[M];
struct edge{int u,v;
} ed[N*M*2];
int cnt;
void getGraph(int a[],int flag){static ull s[M],p[M];memset(s,0,sizeof s);memset(p,0,sizeof p);for (int i=0;i<m;++i){ull x=c[a[i]],t=1ull<<i;for (int j=0;j<64;++j)if (x>>j&1){if (s[j]){x^=s[j];t^=p[j];}else{s[j]=x;p[j]=t;break;}}}for (int i=1;i<=n;++i){ull x=c[i],t=0;for (int j=0;j<64 && x;++j)if (x>>j&1){x^=s[j];t^=p[j];}for (int j=0;j<m;++j)if (t>>j&1 && a[j]!=i)ed[cnt++]=(flag==1?(edge){a[j],i}:(edge){i,a[j]});}
}struct EDGE{int to;ll c;EDGE *las;
} e[(N*M*2+N)*2];
int ne;
EDGE *last[N];
int S,T;
void link(int u,int v,ll c){e[ne]={v,c,last[u]};last[u]=e+ne++;
}
#define rev(ei) (e+(int((ei)-e)^1))
int dis[N],gap[N],BZ;
EDGE *cur[N];
ll dfs(int x,ll s){if (x==T)return s;ll have=0;for (EDGE *ei=cur[x];ei;ei=ei->las){cur[x]=ei;if (ei->c && dis[ei->to]+1==dis[x]){ll t=dfs(ei->to,min(s-have,ei->c));ei->c-=t,rev(ei)->c+=t,have+=t;if (have==s)return s;}}cur[x]=last[x];if (!--gap[dis[x]])BZ=0;++dis[x];++gap[dis[x]];return have;
}
void flow(){BZ=1;while (BZ)dfs(S,INF);
}int p[N];
ll need(int x,int y){return (ll)(v[x]-y)*(v[x]-y);}
int vis[N],bz;
void find(int x){vis[x]=bz;for (EDGE *ei=last[x];ei;ei=ei->las)if (ei->c && vis[ei->to]!=bz)find(ei->to);
}
void divide(int lv,int rv,int p[],int n,edge ed[],int m){static int tmpp[N];static edge tmped[N*M*2];if (n==0)return;if (lv==rv){for (int i=0;i<n;++i)y[p[i]]=lv;return;}int mid=lv+rv>>1;ne=0;for (int i=0;i<n;++i)last[p[i]]=0;last[S]=last[T]=0;for (int i=0;i<m;++i){link(ed[i].u,ed[i].v,INF);link(ed[i].v,ed[i].u,0);}for (int i=0;i<n;++i){ll w=-(need(p[i],mid+1)-need(p[i],mid)); if (w>0)link(S,p[i],w),link(p[i],S,0);else if (w<0)link(p[i],T,-w),link(T,p[i],0);}dis[S]=dis[T]=0;for (int i=0;i<n;++i)dis[p[i]]=0;gap[0]=n+2;flow();gap[dis[S]]--,gap[dis[T]]--;for (int i=0;i<n;++i)gap[dis[p[i]]]--;++bz,find(S);int p0=0,p1=n-1;for (int i=0;i<n;++i)if (vis[p[i]]!=bz)tmpp[p0++]=p[i];elsetmpp[p1--]=p[i];memcpy(p,tmpp,sizeof(int)*n);int e0=0,e1=m-1;for (int i=0;i<m;++i)if (vis[ed[i].u]!=bz && vis[ed[i].v]!=bz)tmped[e0++]=ed[i];else if (vis[ed[i].u]==bz && vis[ed[i].v]==bz)tmped[e1--]=ed[i];memcpy(ed,tmped,sizeof(edge)*m);divide(lv,mid,p,p0,ed,e0);divide(mid+1,rv,p+p1+1,n-1-p1,ed+e1+1,m-1-e1);
}
int main(){freopen("shop.in","r",stdin);freopen("shop.out","w",stdout);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",&v[i]);for (int i=0;i<m;++i)scanf("%d",&a[i]);for (int i=0;i<m;++i)scanf("%d",&b[i]);getGraph(a,1),getGraph(b,-1);for (int i=0;i<n;++i)p[i]=i+1;S=n+1,T=n+2;divide(0,1000000,p,n,ed,cnt);ll ans=0;for (int i=1;i<=n;++i)ans+=need(i,y[i]);printf("%lldn",ans);return 0;
}
本文发布于:2024-01-31 18:22:17,感谢您对本站的认可!
本文链接:https://www.4u4v.net/it/170669654030461.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
留言与评论(共有 0 条评论) |