[BZOJ5289][贪心]HNOI2018:排列

本文详细解析了BZOJ5289题目的解题思路,通过构建一棵树来表达题目中的约束关系,并使用拓扑排序和贪心算法求解最大代价的排列。代码实现中运用了C++的数据结构和算法,如队列、集合和自定义比较函数。

BZOJ5289

膜了一下PIPIBOSS的题解
我觉得我自己描述的都没有他好
所以就贴吧:

首先注意到实际上约束关系构成了一棵树
考虑这个排列 pp,编号为 a[i]a[i] 的出现了,ii 才可以出现
那么如果连边 (a[i],i)(a[i],i),就会构成一棵以 00 为根的树,每一个点只有一个父亲
否则就不合法

因为要父亲被选入,这个点才能被选入,所以排列 pp,相当于是这棵树的一种合法的拓扑序
要求的就是代价最大的一个拓扑序
那么问题就和 POJ2054POJ2054 一样的做法了,用一个神奇的贪心

每次找出全局的权值最小值,往父亲合并,合并成新节点,权值为平均值,即 ∑wisize∑wisize
答案加上被合并的点的权值乘以父亲的 sizesize
正确性感性理解一下,具体证明和国王游戏差不多,发现 swapswap 之后不会更优

果然set被卡了

Code:

#pragma GCC opztime(2)
#include<bits/stdc++.h>
#define ri register
#define ll long long
using namespace std;
inline int read(){
	int res=0,f=1;char ch=getchar();
	while(!isdigit(ch)) {if(ch=='-') f=-f;ch=getchar();}
	while(isdigit(ch)) {res=(res<<1)+(res<<3)+(ch^48);ch=getchar();}
	return res*f;
}
const int N=1e6+5;
struct node{ll v;int s,x;}p[N];
int vis[N<<1],head[N<<1],nxt[N<<1],tot=0;
int n,a[N],ff[N],cnt=0,w[N],in[N],fa[N];
inline void add(int x,int y){vis[++tot]=y;nxt[tot]=head[x];head[x]=tot;}
inline bool topsort(){
	queue<int>q;
	q.push(0);
	while(!q.empty()){
		int x=q.front();q.pop();
		for(int i=head[x];i;i=nxt[i]) if(!--in[vis[i]]) q.push(vis[i]);
	}
	for(ri int i=1;i<=n;i++) if(in[i]>0) return 0;
	return 1;
}
inline bool operator < (const node &a,const node &b){if(a.v*b.s==b.v*a.s) return a.x<b.x;return a.v*b.s<b.v*a.s;}
set<node>s;
inline int get(int v){return v==ff[v]?v:ff[v]=get(ff[v]);}
int main(){
	n=read();
	for(ri int i=1;i<=n;i++) a[i]=read(),add(a[i],i),in[i]++,fa[i]=a[i];
	for(ri int i=1;i<=n;i++) w[i]=read();
	if(!topsort()) {puts("-1");return 0;}
	for(ri int i=1;i<=n;i++){
		p[i].v=w[i],p[i].s=1,p[i].x=i;
		s.insert(p[i]);ff[i]=i;
	}
	cnt=n;p[0].s=1;
	ll ans=0;node t;
	while(!s.empty()){
		t=*s.begin();s.erase(t);
		int y=get(fa[t.x]);
		ans+=t.v*p[y].s;
		if(y){
			s.erase(p[y]);node e=t;
			e.v+=p[y].v;e.s+=p[y].s;e.x=++cnt;
			ff[cnt]=cnt;ff[y]=cnt;ff[get(t.x)]=cnt;
			fa[cnt]=fa[y];fa[t.x]=cnt;
			p[cnt]=e;s.insert(e);
		}
	  	else ff[get(t.x)]=0,p[0].s+=t.s;
	}
	cout<<ans<<endl;
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值