GMOJ 6851. 【2020.11.04提高组模拟】数据恢复

题目大意

题解

这题考场想了很久,但还是没有做出来。

正解也不难想,容易发现对于每一个点,紧接在这个点后面的点肯定是他的儿子节点。

然后类比菊花图的情况,不难发现将这个点后面\(b_i/a_i\)最大的点接在后面是最优的。

于是做法就出来了,用堆维护\(b_i/a_i\)的值,每次找出最大的,将这个点接在他父亲的后面,然后将这两个点合并成一个点。

问题就只剩下合并后的点的权值了,用类似的方法化简一下式子,可以发现如果有一个点\(z\)要排在\(x\)\(y\)合并成的点前面,必然有:

\[\frac{b_z}{a_z}>\frac{b_x+b_y}{a_x+a_y} \]

所以可以发现,两个合并后的a,b值就是两个点的a,b值之和。

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 500000
#define ll long long
using namespace std;
int n,fa[N],i,num,heap[N],next[N],np[N],x,tot,fap[N],fax,sonx,id[N];
ll ans,sum;
struct node{
	ll a,b;
}fp[N],gp[N];
int cmp(int x,int y){
	return fp[x].b*fp[y].a>fp[x].a*fp[y].b;
}
void work(int x){
	int tp=x,s;
	while (tp>=1&&cmp(heap[tp],heap[tp/2])){
		swap(heap[tp],heap[tp/2]);
		id[heap[tp]]=tp;id[heap[tp/2]]=tp/2;
		tp/=2;
	}
	while ((tp*2<=num)&&(cmp(heap[tp],heap[tp*2])==0||(tp*2+1<=num&&cmp(heap[tp],heap[tp*2+1])==0))){
		if (tp*2+1>num||cmp(heap[tp*2],heap[tp*2+1])) s=tp*2;
		else s=tp*2+1;
		swap(heap[tp],heap[s]);
		id[heap[tp]]=tp;id[heap[s]]=s;
		tp=s;
	}
}
void insert(int x){
	heap[++num]=x;
	id[x]=num;
	work(num);
}
void delet(){
	heap[1]=heap[num];
	num--;
	work(1);
}
int getson(int x){
	if (np[x]==x) return x;
	np[x]=getson(np[x]);
	return np[x];
}
int getfa(int x){
	if (fap[x]==x) return x;
	fap[x]=getfa(fap[x]);
	return fap[x];
}
int main(){
	freopen("data.in","r",stdin);
	freopen("data.out","w",stdout);
	scanf("%d",&n);
	for (i=2;i<=n;i++) scanf("%d",&fa[i]),fap[i]=fa[i];
	for (i=1;i<=n;i++) scanf("%lld%lld",&fp[i].a,&fp[i].b),insert(i),np[i]=fap[i]=i,gp[i]=fp[i];
	for (i=1;i<=n;i++){
		x=heap[1];
		delet();
		if (x!=1){
			sonx=getson(fa[x]);
			next[sonx]=x;
			np[sonx]=x;
			
			fap[x]=sonx;
			fax=getfa(x);
			fp[fax].a+=fp[x].a;
			fp[fax].b+=fp[x].b;
			work(id[fax]);
		}
	}
	i=next[1];
	sum=gp[1].b;
	while (i){
		ans+=gp[i].a*sum;
		sum+=gp[i].b;
		i=next[i];
	}
	printf("%lld\n",ans);
	return 0;
}
posted @ 2020-11-05 21:56  Mohogany  阅读(131)  评论(0编辑  收藏  举报