【洛谷P5114】八月脸(Minkowski和)(边分治)

本题为洛谷Rank1难题,由已退役的shadowice1984命题,涉及斜率优化和上凸壳求解。通过边分治获取路径权值,再利用MinkowskiSum合并上凸壳,解决复杂的路径最优化问题。

传送门


解析:

先就本题聊两句:本题出题人shadowice1984退役了,对,就是sjzez那位大毒瘤shadowice1984,每次打他id我都要抱怨这名字怎么这么长的的那位shadowice1984,就是那个常年活跃在洛谷各大毒瘤题题解区和讨论区的那个shadowice1984。TA退役了。

也正是看了他的退役记后我才决定要把这道我本来想着永远咕掉的毒瘤题写出来,并卡到洛谷rank1(也做到了),算是对这位毒瘤的一种纪念吧。

正题:

首先我们撇开那个可(gui)爱(chu)的动图不谈。(为什么这么多可爱的妹子重叠在一起这么鬼畜啊啊啊啊)

显然这是一个斜率优化的式子,答案一定在上凸壳上,那么我们想的是求出所有路径的权值后求一个凸包 ,想办法把所有在凸包上的路径搞出来。

首先这种路径最优化问题显然需要上树分治,这道题点分治会发现不好写(似乎复杂度都是错的?我觉得是,没有细想),我们直接用边分治搞出两个部分的上凸壳,然后用Minkowski Sum合并就行了。

注意在边分治的转化过程中,不注意处理就会丢掉信息。

原题题解中的做法是手动将LCA处的信息补上去。但是其实我们可以转成边权(见下方代码),然后在最终的答案中实际上每个点是算了两倍的,除以2就行了。


代码:

#include<bits/stdc++.h>
#define ll long long
#define re register
#define gc get_char
#define cs const

namespace IO{
	inline char get_char(){
		static cs int Rlen=1<<22|1;
		static char buf[Rlen],*p1,*p2;
		return (p1==p2)&&(p2=(p1=buf)+fread(buf,1,Rlen,stdin),p1==p2)?EOF:*p1++;
	}
	
	template<typename T>
	inline T get(){
		re char c;
		re bool f=0;
		while(!isdigit(c=gc()))f=c=='-';re T num=c^48;
		while(isdigit(c=gc()))num=(num+(num<<2)<<1)+(c^48);
		return f?-num:num;
	}
	inline int getint(){return get<int>();}
}
using namespace IO;

using std::cerr;
using std::cout;

struct Point{
	ll x,y;
	Point(){}
	Point(cs ll &_x,cs ll &_y):x(_x),y(_y){}
	friend Point operator+(cs Point &a,cs Point &b){return Point(a.x+b.x,a.y+b.y);}
	friend Point operator-(cs Point &a,cs Point &b){return Point(a.x-b.x,a.y-b.y);}
	friend ll operator*(cs Point &a,cs Point &b){return a.x*b.y-a.y*b.x;}
};

template<int SIZE>
struct Polygon{
	Point p[SIZE],d[SIZE];
	int siz;
	
	inline void push_back(cs Point &a){p[siz++]=a;}
	
	inline void build_convex(){
		std::sort(p,p+siz,[&](cs Point &a,cs Point &b){return a.x<b.x;});
		int re i=0;
		for(int re j=0;++j<siz;){
			while(d[i]=p[j]-p[i],i&&d[i]*d[i-1]<=0)--i;
			if(!i&&!d[0].x&&d[0].y>=0)--i;
			p[++i]=p[j];
		}
		siz=i;
	}
	
	template<int S1,int S2>
	inline void merge(Polygon<S1> &F,Polygon<S2> &G){
		if(!F.siz||!G.siz)return ;
		F.build_convex();
		G.build_convex();
		Point *i=p+siz++;
		*i=F.p[0]+G.p[0];
		Point *q=std::merge(F.d,F.d+F.siz,G.d,G.d+G.siz,i+1,[&](cs Point &a,cs Point &b){return a*b<0;});
		while(++i<q)*i=(*i)+*(i-1);
		siz=q-p;
	}
	
	inline void print(){
		cerr<<"-----------------------------\n";
		cerr<<"siz : "<<siz<<"\n";
		for(int i=0;i<siz;++i)cerr<<p[i].x<<" "<<p[i].y<<"\n";
		cerr<<"-----------------------------\n"; 
	}
	
	inline ll query(ll k){
		int i=std::upper_bound(d,d+siz,-k,[&](ll k,cs Point &a){return a.y<k*a.x;})-d;
		return p[i].x*k+p[i].y>>1;
	}
};

cs int N=1e5+5;

int n,m;
std::vector<int> E[N];

Polygon<N> H[4],*cur;
Polygon<N*20> ans;

int last[N<<1],nxt[N<<2],to[N<<2],ecnt=1;
Point w[N<<1],val[N];
inline void addedge(int u,int v,cs Point &val=Point(0,0)){
	nxt[++ecnt]=last[u],last[u]=ecnt,to[ecnt]=v;
	nxt[++ecnt]=last[v],last[v]=ecnt,to[ecnt]=u;
	w[ecnt>>1]=val;
}

inline void rebuild(int u,int fa){
	int son=E[u].size(),nd=u;
	for(int re v:E[u])if(v^fa){
		if(--son>2){
			addedge(nd,++n);
			addedge(nd=n,v,val[u]+val[v]);
		}
		else {
			addedge(nd,v,val[u]+val[v]);
			nd=u;
		}
		rebuild(v,u);
	}
}

bool c[N],ban[N<<1];
int siz[N<<1],son[N<<1],pre[N<<1];

int get_siz(int u,int fa){
	siz[u]=u<=n;int mx=0;
	for(int re e=last[u],v=to[e];e;v=to[e=nxt[e]])if(v!=fa&&!ban[e>>1]){
		int t=get_siz(v,u);
		if(mx<t)mx=t,son[u]=v;
		siz[u]+=t,pre[v]=e;
	}
	return siz[u];
}

void dfs(int u,int fa,cs Point &p){
	if(u<=n)cur[c[u]].push_back(p+val[u]);
	for(int re e=last[u],v=to[e];e;v=to[e=nxt[e]])
	if(v!=fa&&!ban[e>>1])dfs(v,u,p+w[e>>1]); 
}

void divide(int u){
	int S=get_siz(u,0),v,e;
	while(siz[v=son[u]]>(S>>1))u=v;
	if(siz[u]>=S-siz[v])e=pre[v];
	else v=to[e=pre[u]^1];
	ban[e>>1]=true;
	H[0].siz=H[1].siz=H[2].siz=H[3].siz=0;
	cur=H;dfs(u,0,Point(0,0));
	cur+=2;dfs(v,0,w[e>>1]);
	bool uok=H[0].siz&&H[1].siz,vok=H[2].siz&&H[3].siz;
	ans.merge(H[0],H[3]);
	ans.merge(H[1],H[2]); 
	if(uok)divide(u);
	if(vok)divide(v);
}

signed main(){
//	freopen("august.in","r",stdin);//freopen("august.out","w",stdout);
	n=getint(),m=getint();
	for(int re i=1;i<=n;++i)val[i].x=getint();
	for(int re i=1;i<=n;++i)val[i].y=getint();
	for(int re i=1;i<=n;++i)c[i]=getint();
	for(int re i=1,u,v;i<n;++i,u=getint(),v=getint(),E[u].push_back(v),E[v].push_back(u));
	int n=::n;rebuild(1,0);
	::n=n;divide(1);
	ans.build_convex();
	while(m--){
		ll k=getint();
		cout<<ans.query(k)<<"\n";
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值