[HNOI/AHOI2018]排列(贪心)

opening

题目链接

好像之前模拟赛考过原题,不过这个题的题意是真的看不懂,还有题解区都说省选出原题,原来省选都会出原题的呀,我吐了。

outline

给数组 \(a\)\(0 <= a_i <= n\)) 和 \(w\),它的一个排列合法当且仅当对于 \(p[j] <= p[k]\),那么不存在 \(a_{p[j]} == p[k]\),定义该排列的价值为 \(\sum_{1}^{n}i*w_{p_i}\),要求是价值最大化。

solution

反正在没有看题解之前我连题意都读不懂,u1s1,这道省选题出的是真的烂啊,不过题中的套路还是不错的。

我们可以把题意转化为对于 \(a[i] = k\),那么在 \(p\) 数组里 \(k\) 要放到 \(i\) 的前面,我们考虑建图,对于这样的情况我们 \(k->i\),即 \(k\) 连向 \(i\)

图如果是一棵基环树,那么本题无解,否则是一棵以 \(0\) 为根的树,题目转化为给定一棵树,每个节点都有一个价值,要求选一个节点必须先选它的父亲,求最大价值。

我们不难想到经典的套路题,取出当前 \(w\) 最小的点,如果它已经没有父亲了,那么直接计算答案,否则把它和它的父亲合并成一个点,且新点的权值定义为它们权值的平均数,然后重复该过程,得到的便是答案。

对于为啥是权值的平均值,这个是可以推柿子的,因为每次合并相当于两个序列合并,随便推一下柿子就可以得到平均值的结论了。

其实这道题的实现方法还是挺值得研究的,主要是利用堆来实现,并且用并查集来维护联通块和最新的情况,方便即时排除堆中过时的元素,利用0号节点方便直接计算答案。

std


// by longdie 
#include <bits/stdc++.h> 
#define ll long long 
using namespace std; 
const int N = 5e5 + 5; 
ll ans, w[N]; 
int n, a[N], fa[N], siz[N]; 
struct node {
	int id; ll val, num; 
	node() {}
	node(int a, ll b, ll c) { id = a, val = b, num = c; } 
	friend bool operator < (const node &a, const node &b) {
		return a.val * b.num > b.val * a.num; 
	}
}; priority_queue<node> q; 
inline int find(int x) { return x == fa[x] ? x : fa[x] = find(fa[x]); } 
inline void merge(int x, int y) { fa[find(x)] = find(y); } 
signed main() {
	scanf("%d", &n); 
	for(register int i = 0; i <= n; ++i) fa[i] = i, siz[i] = 1; 
	for(register int i = 1; i <= n; ++i) {
		scanf("%d", &a[i]); 
		if(find(a[i]) == find(i)) return puts("-1"), 0; 
		merge(a[i], i); 
	}
	for(register int i = 0; i <= n; ++i) fa[i] = i, siz[i] = 1; 
	for(register int i = 1; i <= n; ++i) 
		scanf("%lld", &w[i]), q.push(node(i, w[i], 1));
	while(q.size()) {
		node t = q.top(); q.pop(); 
		int u = find(t.id), v = find(a[u]); 
		if(t.num != siz[u]) continue; 
		ans += w[u] * siz[v]; 
		w[v] += w[u], siz[v] += siz[u]; 
		merge(u, v); 
		if(v) q.push(node(v, w[v], siz[v]));  
	}
	cout << ans << '\n'; 
	return 0; 
}
posted @ 2021-03-11 21:23  longdie  阅读(52)  评论(0编辑  收藏  举报