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

  • 相关阅读:
    检测ip是否ping通和ssh端口是否通
    python从excel取值
    后k8s时代-微服务
    nginx 笔记-01
    Linux中后台执行的方法nohup和screen
    为什么要自定义Java类加载器
    Synchronized实现原理
    一个线程两次或者多次调用start()方法会怎么样
    Java里锁的种类的总结
    Spring事务控制(PROPAGATION_NESTED)
  • 原文地址:https://www.cnblogs.com/Tiw-Air-OAO/p/13033843.html
Copyright © 2020-2023  润新知