把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【洛谷6621】[省选联考 2020 A 卷] 魔法商店(保序回归问题)

点此看题面

  • \(n\)个商品,每个商品有一个魅力值\(c_i\)和一个价值\(v_i\)
  • 规定一个商品集合是好的,当且仅当任意子集魅力值异或和不为\(0\),且是符合条件的元素最多的集合。
  • 给定两个好的商品集合\(A,B\),要求修改若干商品的价值(把\(v_i\)变成\(v_i\pm x\)的代价为\(x^2\)\(x\)为整数),使得\(A,B\)分别是代价总和最小和代价总和最大的好集合。
  • \(n\le10^3,m\le64\)

保序回归问题

这道题一个劝退的前置知识:浅谈保序回归问题

其实只要知道什么是保序回归问题,且知道可以利用整体二分+网络流来求解即可。

具体地,由于此题要求\(x\)为整数,尽管是\(L_2\)问题,依然可以取中点\(mid\)每次求解其对应的\(S=\{mid,mid+1\}\)问题。

显然\(mid\)\(mid+1\)的差值为\((v_i-mid)^2-(v_i-mid-1)^2=2(v_i-mid)-1\)

构建偏序关系

先考虑集合\(A\)(集合\(B\)同理),它是代价总和最小的好集合,当且仅当其中任意一个元素都不能被另一个比它代价更小的元素替换。

这也就等价于所有能替换它的元素代价都不小于它。

由于好集合必须是元素最多的集合,我们只需把这个元素删去,剩余元素构建线性基,则此时能插入的线性基就是可以替换它的元素。

这样一来就成功构建了偏序关系,然后直接套用保序回归问题的解法即可。

代码:\(O(n^2mlogV)\)

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 1000
#define LG 64
#define INF 1e9
#define pb push_back
#define ull unsigned long long
using namespace std;
int n,m,a[N+5],b[N+5],v[N+5],f[N+5];ull c[N+5];vector<int> G[N+5];vector<int>::iterator it;
struct Basis
{
	ull v[N+5];I void Cl() {for(RI i=0;i^LG;++i) v[i]=0;}
	I void A(ull x) {for(RI i=LG-1;~i;--i) if(x>>i&1) {if(!v[i]) {v[i]=x;break;}x^=v[i];}}
	I bool T(ull x) {for(RI i=LG-1;~i;--i) if(x>>i&1) {if(!v[i]) return 1;x^=v[i];}return 0;}
}B;
namespace D//网络流
{
	#define PS (N+2)
	#define ES (2*N+2*N*N)
	#define E(x) ((((x)-1)^1)+1)
	#define add(x,y,f) (e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y,e[ee].F=f)
	int s,t,ee,lnk[PS+5],cur[PS+5];struct edge {int to,nxt,F;}e[2*ES+5];
	I void Init(CI x) {s=x+1,t=x+2,ee=0;for(RI i=1;i<=t;++i) lnk[i]=0;}
	I void Add(CI x,CI y,CI f) {add(x,y,f),add(y,x,0);}
	int q[PS+5],D[PS+5];I bool BFS()
	{
		RI i,k,H=1,T=1;for(i=1;i<=t;++i) D[i]=0;D[q[1]=s]=1;W(H<=T&&!D[t])
			for(i=lnk[k=q[H++]];i;i=e[i].nxt) e[i].F&&!D[e[i].to]&&(D[q[++T]=e[i].to]=D[k]+1);
		return memcpy(cur,lnk,sizeof(cur)),D[t];
	}
	I int DFS(CI x=s,RI f=INF)
	{
		if(x==t||!f) return f;RI i,g,res=0;for(i=cur[x];i;i=e[i].nxt)
		{
			if(!e[i].F||D[e[i].to]^(D[x]+1)||!(g=DFS(e[i].to,min(f,e[i].F)))) continue;
			if(e[i].F-=g,e[E(i)].F+=g,res+=g,!(f-=g)) break;
		}return cur[x]=i,res;
	}
	I void MaxFlow() {W(BFS()) DFS();}I bool Chk(CI x) {return !D[x];}//Chk判断保留哪边
}
int p[N+5],pl[N+5],pr[N+5],id[N+5];I void Solve(CI l,CI r,CI L,CI R)//整体二分
{
	RI i;if(l>r||L==R) {for(RI i=l;i<=r;++i) f[p[i]]=L;return;}//边界
	for(i=l;i<=r;++i) id[p[i]]=i-l+1;//标号
	RI c,mid=L+R>>1;for(D::Init(r-l+1),i=l;i<=r;++i)//枚举所有元素
	{
		(c=2*(v[p[i]]-mid)-1)>0?D::Add(D::s,id[p[i]],c):D::Add(id[p[i]],D::t,-c);//根据代价与源汇点连边
		for(it=G[p[i]].begin();it!=G[p[i]].end();++it) id[*it]&&(D::Add(id[p[i]],id[*it],INF),0);//根据偏序关系连边
	}
	RI tl=0,tr=0;for(D::MaxFlow(),i=l;i<=r;++i) (D::Chk(id[p[i]])?pl[++tl]:pr[++tr])=p[i],id[p[i]]=0;//判断每个点在哪个集合
	for(i=1;i<=tl;++i) p[l+i-1]=pl[i];for(i=1;i<=tr;++i) p[l+tl+i-1]=pr[i];Solve(l,l+tl-1,L,mid),Solve(l+tl,r,mid+1,R);//划分点集,递归
}
int main()
{
	RI i,j;for(scanf("%d%d",&n,&m),i=1;i<=n;++i) scanf("%llu",c+i);for(i=1;i<=n;++i) scanf("%d",v+i);
	for(i=1;i<=m;++i) scanf("%d",a+i);for(i=1;i<=m;++i) scanf("%d",b+i);
	for(i=1;i<=m;++i) {for(B.Cl(),j=1;j<=m;++j) i^j&&(B.A(c[a[j]]),0);for(j=1;j<=n;++j) a[i]^j&&B.T(c[j])&&(G[a[i]].pb(j),0);}//构建A的偏序关系
	for(i=1;i<=m;++i) {for(B.Cl(),j=1;j<=m;++j) i^j&&(B.A(c[b[j]]),0);for(j=1;j<=n;++j) b[i]^j&&B.T(c[j])&&(G[j].pb(b[i]),0);}//构建B的偏序关系
	ull ans=0;for(i=1;i<=n;++i) p[i]=i;for(Solve(1,n,0,1e6),i=1;i<=n;++i) ans+=1ull*(f[i]-v[i])*(f[i]-v[i]);//根据整体二分后确定的f值计算答案
	return printf("%llu\n",ans),0;
}
posted @ 2021-04-08 18:07  TheLostWeak  阅读(183)  评论(0编辑  收藏  举报