联合省选2020 魔法商店

魔法商店

笠笠和伦伦来到了一家魔法商店,这家商店内有 \(n\) 件礼品,礼品从 \(1\sim n\) 编号,\(i\) 号礼品的魅力值为 \(c_i\),价格为 \(v_i\)

两人希望购买一些礼品,但他们的要求比较奇怪:假设购买到的礼品集合为 \(S = \{s_1, s_2, \dots, s_p\}(1\le s_i\le n)\),两人要求对于 \(S\) 中任意的非空子集 \(T = \{t_1, t_2, \dots, t_q\}\),它包含的所有礼品的魅力值异或和都不为零,即:\(c_{t_1} \oplus c_{t_2} \oplus \cdots \oplus c_{t_q} \neq 0\)。其中 \(\oplus\) 是异或运算。在此基础上,两人还要求购买到的礼品数尽可能多

例如:\(c_1 = 1,c_2 = 2,c_3 = 5,c_4 = 6,c_5 = 7\)。则 \(S_1 = \{2,3,5\}\) 不符合要求,因为 \(c_2 \oplus c_3 \oplus c_5 = 0\)\(S_2 = \{1,2,3\}\)\(S_3 = \{2, 4, 5\}\) 符合要求,其任意非空子集的异或和都不为零。\(S_4 = \{1, 2\}\) 因为其包含的礼品数不是最多的。

满足两人要求的礼品集合可能很多,因此商店老板为两人挑选出了两个符合要求的礼品集合 \(A\)\(B\)(显然它们所含的礼品数相同),伦伦喜欢集合 \(A\),但笠笠更喜欢集合 \(B\)。为了笠笠同意购买集合 \(A\),伦伦决定使用魔法改变礼品价格。更具体地,伦伦能花费 \((x - v_i)^2\) 的魔力值,将 \(i\) 号礼品的价格改为任意整数 \(x\),每件礼品只能被改价一次。

伦伦希望改价后 \(A\) 是所有符合要求的礼品集合之中价格总和最小的,且 \(B\) 是所有符合要求的礼品集合之中价格总和最大的(一个礼品集合的价格总和为它包含的所有礼品的价格之和)。现在请你帮伦伦计算,他至少要花费多少魔力值才能完成他的目标。

对于所有测试数据:\(1\le n\le 1000\)\(1\le m\le 64\)\(1\le c_i < 2^{64}\)\(0\le v_i\le 10^6\)

保序回归问题

https://www.luogu.com.cn/blog/Hakurei-Reimu/solution-p6621

保序回归问题是指,对于一个正整数 \(p\) ,给定一个偏序关系(有向无环图),图中每个点 \(i\) 有权值 \((w_i,y_i)\) ,你要给每个点赋上另一个权值 \(f_i\) ,使得图中任意一条有向路径上的 \(f_i\) 递增,并最小化回归代价:

\[\sum_{i\in U} w_i|f_i-y_i|^p \]

我们把上述问题称作 \(L_p\) 问题。

\(L_p\) 均值与其性质

我们定义 \(L_p\) 均值为使 \(\sum\limits_{i\in U}w_i|k-y_i|^p\) 最小的 \(k\) ,也就是所有 \(f_i\) 相等时的最优取值。对目标式求导,解出其零点就是 \(L_p\) 均值,这里给出 \(L_1\) 均值为 \(y_i\) 的带权中位数, \(L_2\) 均值为 \(y_i\) 的带权平均数 ( \(\left(\sum\limits_{i\in U}w_iy_i\right)\left/\left(\sum\limits_{i\in U}w_i\right)\right.\) )。可以证明,当 \(p>1\) 时, \(L_p\) 均值是唯一的;且 \(L_p\) 问题一定存在一组最优解,使得对于任意 \(f_i\) 都可以找到某个点集,使它的 \(L_p\) 均值等于 \(f_i\)

\(f_i\)可以只取是某点集\(L_p\)均值的值。否则为什么要取呢?

整体二分

应用整体二分的思想,可以得到一个求问题的近似解的算法。

我们在原问题的基础上加入一些限制。定义 \(solve(U,l,r)\) 表示当前点集为 \(U\) ,且 \(f_i\) 的取值在区间 \([l,r]\) 内的 \(L_p\) 问题。

注意这是新加的限制,现在的最优解也是限制了取值范围之后的最优解(一定存在)。

\(mid=(l+r)/2\) ,如果我们能够知道 \(U\) 的任意一组最优解中,有哪些点是 \(\leq mid\) 的,就可以将当前问题划分成两个规模减半的子问题了。

尝试构造另一个问题。设开区间 \((a,b)\)\([l,r]\) 包含,且 \(U\) 的任意非空子集的 \(L_p\) 均值都不在 \((a,b)\) 内。这个性质保证了对于任意的子问题,都存在一组最优解满足其所有元素均不在 \((a,b)\) 中。下面给出一个引理:

\(solve(U,a,b)\) 的一组最优解为 \(\tilde z\) ,且满足 \(\forall i\in U,\tilde z_{i}\notin (a,b)\) 。则存在 \(solve(U,l,r)\) 的一组最优解 \(z\) ,使得 \(z_i\leq a\) 当且仅当 \(\tilde z_i=a\)

“当且仅当”换做符号表示是\(\Leftrightarrow\),所以也有\(z_i\geq b\Leftrightarrow \tilde z_i=b\)

回到原问题,考虑找到满足性质的区间 \((mid,mid+\epsilon)\) ,那么就可以由 \(solve(U,mid,mid+\epsilon)\) 的最优解来确定 \(U\) 中的哪些点在原问题的最优解中是 \(\leq mid\) 的。但直接找到这样的区间比较麻烦,可能还需要跑浮点网络流,会遇到精度等一系列问题。

由于 \(U\) 的任意非空子集的 \(L_p\) 的并集是有限集,所以一定存在足够小的 \(\epsilon\) 使得区间中没有这些元素。当 \(\epsilon\) 无限趋近于 \(0\) 时,节点 \(i\) 的权值为 \(w_i[(mid+\epsilon-y_i)^p-(mid-y_i)^p]\) 。把所有点的权值除以 \(\epsilon\) ,最优方案不变,权值变为 \(w_i(mid-y_i)^p\)\(mid-y_i\) 处的导数。当 \(p=2\) 时,权值为 \(2w_i(mid-y_i)\) ,于是问题得到了完美的解决。

本题题解

题目中要求了大小关系的子集都是给定的 \(n\) 个 01 向量的极大线性无关组。所以从某一组向量中拿走一个,线性空间维数会减一,也就能再加入一个向量。因此任意两组向量之间是可以由替换元素这个操作来互相转化的,并且替换的顺序没有限制。于是我们只需要从向量组 \(A\) 的每个元素向能替换它的元素连边,向量组 \(B\) 反之,即可构造出偏序关系。然后就可以跑 \(L_2\) 问题了。

然后有个问题就是这题要求修改后的价格为整数,可以这样处理:当 \(r-l=1\) 时,仍然是答案只有两种取值的情况,因此仍可以用转化成最小闭合子图问题,相应地每个点的权值设为选 \(r\) 与选 \(l\) 的代价差即可。

\((l+1-w_i)^2-(l-w_i)^2=2(l-w_i)+1\)

时间复杂度\(O(m^2n+\text{flow}(n,n^2)\log n)\)

CO int N=1e3+10,inf=1e9;
namespace FLOW{
	int S,T;
	struct edge {int y,c,a;};
	vector<edge> to[N];
	int dis[N];
	
	void init(int n){
		S=n-1,T=n;
		for(int i=1;i<=n;++i) to[i].clear();
	}
	IN void link(int x,int y,int c){
		to[x].push_back({y,c}),to[y].push_back({x,0});
		to[x].back().a=to[y].size()-1,to[y].back().a=to[x].size()-1;
	}
	bool bfs(){
		fill(dis+1,dis+T+1,inf),dis[S]=0;
		deque<int> que{S};
		while(que.size()){
			int x=que.front();que.pop_front();
			for(CO edge&e:to[x])if(e.c and dis[e.y]==inf)
				dis[e.y]=dis[x]+1,que.push_back(e.y);
		}
		return dis[T]<inf;
	}
	int dfs(int x,int lim){
		if(x==T) return lim;
		int rest=lim;
		for(edge&e:to[x])if(e.c and dis[e.y]==dis[x]+1){
			int delta=dfs(e.y,min(rest,e.c));
			if(delta==0) {dis[e.y]=inf; continue;}
			rest-=delta,e.c-=delta,to[e.y][e.a].c+=delta;
			if(rest==0) break; // edit 1
		}
		return lim-rest;
	}
	int main(){
		int ans=0;
		while(bfs()) ans+=dfs(S,inf);
		return ans;
	}
}

namespace BASE{
	uint64 x[64];
	IN void clear(){
		fill(x,x+64,0);
	}
	int insert(uint64 v){
		for(int i=63;i>=0;--i)if(v>>i&1){
			if(x[i]) v^=x[i];
			else return x[i]=v,i;
		}
		return -1;
	}
}

uint64 c[N];
int v[N],a[N],b[N];
list<int> to[N];
int p[N],q[N],f[N];

void solve(int l,int r,int L,int R){
	if(l>r) return;
	if(L+1==R){
		FLOW::init(r-l+3);
		for(int i=l;i<=r;++i) q[p[i]]=i-l+1;
		for(int i=l;i<=r;++i){
			int x=p[i],w=-(2*(L-v[x])+1);
			if(w>0) FLOW::link(FLOW::S,q[x],w);
			else if(w<0) FLOW::link(q[x],FLOW::T,-w);
			for(list<int>::iterator j=to[x].begin();j!=to[x].end();){
				int y=*j;
				if(q[y]) FLOW::link(q[x],q[y],inf),++j;
				else j=to[x].erase(j);
			}
		}
		FLOW::main(),FLOW::bfs();
		for(int i=l;i<=r;++i){
			int x=p[i];
			if(FLOW::dis[q[x]]==inf) f[x]=L; // cut to T
			else f[x]=R;
		}
		for(int i=l;i<=r;++i) q[p[i]]=0;
		return;
	}
	int mid=(L+R)>>1;
	FLOW::init(r-l+3);
	for(int i=l;i<=r;++i) q[p[i]]=i-l+1;
	for(int i=l;i<=r;++i){
		int x=p[i],w=-(mid-v[x]);
		if(w>0) FLOW::link(FLOW::S,q[x],w);
		else if(w<0) FLOW::link(q[x],FLOW::T,-w);
		for(list<int>::iterator j=to[x].begin();j!=to[x].end();){
			int y=*j;
			if(q[y]) FLOW::link(q[x],q[y],inf),++j;
			else j=to[x].erase(j);
		}
	}
	FLOW::main(),FLOW::bfs();
	static int t[N];
	int ql=l-1,qr=r+1;
	for(int i=l;i<=r;++i){
		int x=p[i];
		if(FLOW::dis[q[x]]==inf) t[++ql]=x;
		else t[--qr]=x;
	}
	for(int i=l;i<=r;++i) q[p[i]]=0;
	copy(t+l,t+r+1,p+l);
	solve(l,ql,L,mid);
	solve(qr,r,mid,R);
}
int main(){
	int n=read<int>(),m=read<int>();
	for(int i=1;i<=n;++i) read(c[i]);
	for(int i=1;i<=n;++i) read(v[i]);
	for(int i=1;i<=m;++i) read(a[i]);
	for(int i=1;i<=m;++i) read(b[i]);
	for(int i=1;i<=m;++i){ // find items which a[i] can be replaced with
		BASE::clear();
		for(int j=1;j<=m;++j)if(j!=i) BASE::insert(c[a[j]]);
		for(int j=1;j<=n;++j)if(j!=a[i]){
			int p=BASE::insert(c[j]);
			if(p!=-1) to[a[i]].push_back(j),BASE::x[p]=0;
		}
	}
	for(int i=1;i<=m;++i){
		BASE::clear();
		for(int j=1;j<=m;++j)if(j!=i) BASE::insert(c[b[j]]);
		for(int j=1;j<=n;++j)if(j!=b[i]){
			int p=BASE::insert(c[j]);
			if(p!=-1) to[j].push_back(b[i]),BASE::x[p]=0;
		}
	}
	iota(p+1,p+n+1,1);
	solve(1,n,0,1e6);
	int64 ans=0;
	for(int i=1;i<=n;++i) ans+=(int64)(f[i]-v[i])*(f[i]-v[i]);
	printf("%lld\n",ans);
	return 0;
}

posted on 2020-07-05 19:19  autoint  阅读(221)  评论(0编辑  收藏

导航