@loj


@description@

给定 n 个整数 (a_1, a_2, dots, a_n (0 leq a_i leq n)),以及 n 个整数 (w_1, w_2, dots, w_n)。称 (a_1, a_2, dots, a_n) 的一个排列 (a_{p_1}, a_{p_2}, dots, a_{p_n})(a_1, a_2, dots, a_n) 的一个合法排列,当且仅当该排列满足:对于任意的 (k) 和任意的 (j),如果 (jleq k),那么 (a_{p_j}) 不等于 (p_k)。(换句话说就是:对于任意的 k 和任意的 j,如果 (p_k) 等于 (a_{p_j}),那么 (j < k)。)

定义这个合法排列的权值为 (w_{p_1} + 2w_{p_2} + dots + nw_{p_n})。你需要求出在所有合法排列中的最大权值。如果不存在合法排列,输出 -1。

原题传送门。

@solution@

连边 (a_i -> i) 表示 (a_i) 先于 (i) 出现。连成环无解。
如果有解,一定形成以 0 为根的树。我们相当于从根往叶子选数形成排列。

考虑去掉选数的树形限制,则贪心地把小的放前面。
如果在树上,可以通过交换论证证明,如果当前最小的 (w)(w_x),则选完 x 的父亲一定立刻选 x。此时不妨把 x 与 x 的父亲合并。

考虑合并过后,每个块可以表示成 ((k_i, b_i, s_i)),表示如果该块第一个元素在 x 时刻被选择,则贡献为 (k_ix + b_i),以及块中点数为 (s_i)

去掉选数的树形限制,通过交换论证,发现如果块 i 在块 j 前面,则应满足 (k_is_j < k_js_i),即 (frac{k_i}{s_i} < frac{k_j}{s_j})
一样地,设当前最小的 (frac{k}{s})(frac{k_x}{s_x}),则可以合并 x 与 x 的父亲。

这样一直合并就可以合并成一个结点,也即我们所需要的答案。
用可删除堆维护一下就好了。

@accepted code@

#include <queue>
#include <cstdio>
#include <algorithm>
using namespace std;

typedef long long ll;

const int MAXN = 500000;

int fa[MAXN + 5];
int find(int x) {return fa[x] = (fa[x] == x ? x : find(fa[x]));}
bool unite(int x, int y) {
	int fx = find(x), fy = find(y);
	if( fx != fy ) {
		fa[fx] = fy;
		return true;
	} else return false;
}

struct node{
	ll k, b; int s, i;
	friend bool operator < (const node &a, const node &b) {
		if( a.s*b.k == a.k*b.s )
			return (a.i == b.i) ? a.s < b.s : a.i < b.i;
		return a.s*b.k < a.k*b.s;
	}
	friend bool operator == (const node &a, const node &b) {
		return a.k == b.k && a.b == b.b && a.s == b.s && a.i == b.i;
	}
	friend node operator + (const node &a, const node &b) {
		return (node){a.k + b.k, a.s*b.k + a.b + b.b, a.s + b.s, a.i};
	}
}w[MAXN + 5];

struct heap{
	priority_queue<node>q1, q2;
	void maintain() {
		while( !q1.empty() && !q2.empty() && q1.top() == q2.top() )
			q1.pop(), q2.pop();
	}
	void push(node x) {q1.push(x); maintain();}
	void pop(node x) {q2.push(x); maintain();}
	void pop() {q2.push(q1.top()); maintain();}
	bool empty() {maintain(); return q1.empty();}
	node top() {maintain(); return q1.top();}
}h;

int a[MAXN + 5];
int main() {
	int n; scanf("%d", &n);
	for(int i=1;i<=n;i++) scanf("%d", &a[i]);
	for(int i=1;i<=n;i++) scanf("%lld", &w[i].k), w[i].s = 1, w[i].i = i;
	for(int i=1;i<=n;i++) fa[i] = i;
	for(int i=1;i<=n;i++) {
		if( !unite(i, a[i]) ) {
			puts("-1");
			return 0;
		}
	}
	for(int i=1;i<=n;i++) fa[i] = i;
	for(int i=1;i<=n;i++) h.push(w[i]);
	while( !h.empty() ) {
		node t = h.top(); h.pop();
		int x = t.i, y = find(a[x]); fa[x] = y;
		if( y ) h.pop(w[y]);
		w[y] = w[y] + w[x];
		if( y ) h.push(w[y]);
	}
	printf("%lld
", w[0].k + w[0].b);
}

@details@

这道题本质上是 「SCOI2016」背单词 的加强版,但做法与这道题无关,反倒是与 hdu上的某道题 类似。

可删堆必须要是严格偏序(即要么 a < b,要么 b < a),不然会有问题。所以判定条件要更严格一些。

原文地址:https://www.cnblogs.com/Tiw-Air-OAO/p/13033843.html