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