LOJ2509 [AHOI / HNOI2018] 排列

懒得粘题面…

我们发现限制是一个外向树结构,选一个点必须先选父亲,第 i 次选择一个点,价值是 i*点权。求最大价值。

DP 好像做不了,只能贪心了。考虑点权最小的点 x,一定是紧接着父亲被选。因此我们可以把 x 和父亲合并,看做一个点,同时计算他们之间的贡献。
那么缩点后的若干个联通块怎么安排顺序?类似国王游戏,记 S 为权值和,size 为合并前点数,那么 1 在 s 前面当且仅当 S 1 s i z e 2 &lt; S 2 s i z e 1 S_1size_2&lt;S_2size_1 S1size2<S2size1,就是 S 1 / s i z e i &lt; S 2 / s i z e 2 S_1/size_i&lt;S_2/size_2 S1/sizei<S2/size2。把这个作为合并后的权值即可。

#include<bits/stdc++.h>
#define ll long long
#define pb push_back
#define fir first
#define sec second
#define ld long double
using namespace std;
const int N=500010;
typedef pair <double,int> P;
struct edge {
	int to,next;
}ed[N<<2];
struct node {
	ll w;int size,x;
	node(ll _w=0,int _size=0,int _x=0) {w=_w,size=_size,x=_x;}
};
bool operator < (node a,node b) {return 1ll*a.w*b.size>1ll*b.w*a.size;}
priority_queue <node> q;
int ff[N],size[N],sz,head[N],fa[N],tim,vis[N];
ll w[N];
void add_edge(int from,int to)
{
	ed[++sz].to=to;
	ed[sz].next=head[from];
	head[from]=sz;
}
int read()
{
	int x=0;char c=getchar(),flag='+';
	while(!isdigit(c)) flag=c,c=getchar();
	while(isdigit(c)) x=x*10+c-'0',c=getchar();
	return flag=='-'?-x:x;
}
void dfs(int u)
{
	vis[u]=tim;
	for(int i=head[u];i;i=ed[i].next)
	{
		int v=ed[i].to;
		if(vis[v]==tim) {puts("-1");exit(0);}
		if(vis[v]) continue;
		dfs(v);
	}
}
int find(int x)
{
	int y=x;
	while(y^fa[y]) y=fa[y];
	while(x^y) 
	{
		int tmp=fa[x];
		fa[x]=y;
		x=tmp;
	}
	return y;
}
int main()
{
	ll ans=0;
	int n=read();
	for(int i=1;i<=n;i++) ff[i]=read(),add_edge(i,ff[i]);
	for(int i=1;i<=n;i++) w[i]=read(),fa[i]=i,size[i]=1;
	for(int i=1;i<=n;i++) tim++,dfs(i),q.push(node(w[i],1,i));
	size[0]=1;
	while(q.size())
	{
		node u=q.top();
		q.pop();
		int x=find(u.x);
		if(u.size^size[x]) continue;
		int y=find(ff[x]);
		ans+=1ll*size[y]*w[x];
		w[y]+=w[x];
		size[y]+=size[x];
		fa[x]=y;
		if(y) q.push(node(w[y],size[y],y));
	}
	cout<<ans;
	return 0;
}
/*by DT_Kang*/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值