2020全国统一省选day1 魔法商店

题目


正解

据说是一道论文题……
论文:2018集训队论文高睿泉《浅谈保序回归问题》

保序回归问题:
有一个正整数\(p\),给出一个有向图,点\(i\)有权值\((w_i,y_i)\),需要调整\(y_i\)的值使得\(y_i\)满足有向无环图的偏序关系。调整的代价为前后\(y_i\)的差的\(p\)次方乘\(w_i\),求最小的代价。
形式化地说:给每个点赋一个新的权值\(f_i\),使得每条边\((u,v)\in E\)满足\(f_u\leq f_v\),求\(\sum w_i|f_i-y_i|^p\)的最小值。
这样的问题记作\(L_p\)

其实这种问题之前已经做过一次了:jzoj6734. 【2020.06.18省选模拟】T2 航行
没错就是比赛的前天做到的题
套路做法:整体二分,强制每个权值只能选择\(mid\)\(mid+1\),跑最大(小)权闭合子图,选\(mid\)的点最终的权值\(\leq mid\),选\(mid+1\)的点最终的权值\(>mid\),分开来递归求解。
之前做的那一道题的限制关系比较优美,所以可以DP处理。

最大(小)权闭合子图:
“闭合子图”就是某个点集\(V\),满足对于任意\(u \in V\),对于任意\((u,v)\in E\)都有\(e\in V\)。用人话说就是从\(V\)中的每个点开始遍历,
能够遍历到的每个点都在\(V\)中。每个点上有个权值\(w_i\),要选出一个闭合子图使得点权最大。
一般套路:网络流,建个新图。对于每个点\(i\),如果\(w_i>0\)就连边\((S,i,w_i)\),如果\(w_i<0\)就连边\((i,T,-w_i)\)。原图中的每一条边都在新图中对应地连,容量无穷。
答案为\(\sum_{w_i>0} w_i-最小割\)
理解:如果不考虑限制,则贪心地选所有正权点是最优的。加入限制,对于一个点\(u\),如果它不选,则所有\(v\),满足从\(v\)开始遍历可以走到\(u\),这些\(v\)都不能选。显然所有\(u\)满足这个条件是充分必要条件。
放在图中来看:割掉边\((u,T)\),即选\(v\),或者割掉边\((S,v)\),即不选\(v\)

具体来说,求解最大(小)权闭合子图的时候,如下处理:
将权值选\(mid\)记作“不选”,将权值选\(mid+1\)记作“选”。
对于点\(i\),它的权值设为\(-(w(i,mid+1)-w(i,mid))\)\(w(i,f)\)\(y_i\)变为\(f\)时的代价)。为什么这么设,因为本该跑最小权闭合子图,取个负号就变成最大权闭合子图了。当然也可以不这样设。
如果某个点“选”,那么它能遍历到的所有点都必须“选”。由此得出限制关系,建图。
跑最大权闭合子图。
答案即\(\sum w(i,mid)-最大权\)
具体方案看\(S\)集或\(T\)集即可。

这题的正解大概可以分为两个部分:求出限制关系和求解保序回归问题。
后者上面已经讨论过了,就讲述一下前者。
只考虑\(A\)带来的限制,\(B\)同理。
显然一个礼品集合就是一个线性基。假如有另一个线性基\(C\),它可以通过从\(A\)开始,依次替换线性基中的向量得到,并且保证替换的过程中一直都是线性基。
表示不会证。
假设知道了上面的结论是对的,那么显然我们只需考虑\(A\)被替换了一个元素的情形。于是,对于每个数\(c_i\),找到\(c_i\)能替换哪些元素,钦定它们都要小于等于\(c_i\)
不难发现\(c_i\)能替换哪些元素,等价于\(c_i\)能被表示成\(A\)中的哪些元素异或起来。
这应该是线性基的一个基本操作吧。建线性基的时候,用个bitset来表示线性基中的某个数是原来的哪几个数异或过来。查询的时候,将异或的线性基中的几个数的bitset异或起来。详见代码。
这题\(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];
		else
			tmpp[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("%lld\n",ans);
	return 0;
}
posted @ 2020-06-27 21:26  jz_597  阅读(245)  评论(0编辑  收藏  举报