@loj - 2509@ 「AHOI / HNOI2018」排列


@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\),如果 \(j\leq 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\n", w[0].k + w[0].b);
}

@details@

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

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

posted @ 2020-06-02 21:28  Tiw_Air_OAO  阅读(158)  评论(0编辑  收藏  举报