@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),不然会有问题。所以判定条件要更严格一些。