• 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_uleq 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)都有(ein 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
    ",ans);
    	return 0;
    }
    
  • 相关阅读:
    452.用最少数量的箭引爆气球
    134.加油站
    Javascript
    spring-JDBC模板
    AOP注解方式ApsectJ开发
    AOP通知类型
    AOP的使用
    AOP相关术语
    AOP
    IOC注解详解
  • 原文地址:https://www.cnblogs.com/jz-597/p/13199944.html
Copyright © 2020-2023  润新知