「集训队作业2018」三角形

「集训队作业2018」三角形

zhangzy的神题~

考虑没有树的限制咋做,问题相当于有一些二元组 \((x, y)\) 和一个空的序列,每次选一个二元组填到序列里面,最小化最大前缀和。具体来说,在每个节点放石子相当于两部,1.放上 \(w_i\) ,2.拿掉 \(\sum w_v\)\(v\)\(i\) 在树上的儿子,那么可以看做二元组 \((w_i,-\sum w_v)\)

\(sum\) 表示一段操作石子数量的增量, \(max\) 表示操作序列的石子数量最大前缀和,每次向后添加一个二元组就相当于 \(sum'=sum+\sum w_v,max'=\max(max,max'+w_i)\) 。事实上二元组也可以用 \((sum,max)\) 的形式表示,即 \((w_i-\sum{w_v},w_i)\) ,由上面可以发现这个二元组是可以简易复合的。我们的问题就是给这些二元组安排一个优先级,使得从左到右复合之后 \(max\) 最小。

可以证明,对于二元组 \(A,B\),定义 \(+\) 为复合操作,\(A\) 的优先级比 \(B\) 高当且仅当 \((A+B).max<(B+A).max\) ,且这样保证不存在二元组 \(A,B,C\) 满足 \(A<B,B<C,C<A\) 。证明需要进行分类讨论:

首先考虑复合顺序改变后 \(sum\) 不变,所以可以只考虑复合后的 \(max\) ,显然 \(max\) 越小越好

对于 \(sum\) 为负的二元组,一定比 \(sum\) 为正的二元组优。

对于 \(sum\) 都为负的二元组, \(max\) 越小的优先级越高。

对于 \(sum\) 都为正的二元组,\(sum-max\) 越小的优先级越高。

然后我们就证明了不存在二元组 \(A,B,C\) 满足 \(A<B,B<C,C<A\) ,即可以按照这个优先级给二元组排序,且这样最小化最终的 \(max\) 。这样就解决没有树的限制的情况。

考虑有树的限制,因为每个二元组要所有儿子都放完才能放非常麻烦,所以考虑反转,改为每个二元组要在祖先放完才能放,那么二元组就变成了 \((\sum w_v-w_i,\sum w_v)\) 。也就是拿走点 \(i\) 上的石子然后放上其儿子的石子,相当于倒过来做之前的操作。此时每一个前缀还是对应原来的一个时刻,同样要最小化最大前缀和,但是每一个点只要其父亲放了其就能放,变得容易处理许多。

观察发现,任意两个二元组合并以后优先级至少比之前的一个二元组优先级低,所以对于某一时刻优先级最高的二元组,就算其父亲还没有放,其也会在其父亲被放之后马上被放,所以可以直接将这个二元组与它的父亲合并。那么用一个堆维护一下当前优先级最高的二元组,就得到了根的答案的操作序列。

最后有一个结论,对于任意一个节点,其答案的操作序列一定是根的操作序列的一个子序列,这里不太需要证明,直接从优先级的角度考虑就好了,所以用线段树合并维护每一个子树里的操作序列和结合后的答案即可,总复杂度 \(\mathcal O(n\log n)\)

code

/*program by mangoyang*/
#pragma GCC optimize("Ofast", "inline")
#include<bits/stdc++.h>
#define inf (0x3f3f3f3f)
#define Max(a, b) ((a) > (b) ? (a) : (b))
#define Min(a, b) ((a) < (b) ? (a) : (b))
typedef long long ll;
using namespace std;
template <class T>
inline void read(T &x){
	int ch = 0, f = 0; x = 0;
	for(; !isdigit(ch); ch = getchar()) if(ch == '-') f = 1;
	for(; isdigit(ch); ch = getchar()) x = x * 10 + ch - 48;
	if(f) x = -x;
}
const int N = 200005;
vector<int> vec[N], g[N];
ll w[N], w2[N], ans[N];
int vis[N], pa[N], b[N], c[N], rt[N], fa[N], type, n, cnt;
struct Node{
	ll x, y; int id;
	friend Node operator + (const Node &A, const Node &B){
		return (Node){A.x + B.x, max(A.y, A.x + B.y), A.id};
	}
	friend bool operator < (const Node &A, const Node &B){
		ll tmp1 = max(A.y, A.x + B.y);
		ll tmp2 = max(B.y, B.x + A.y);
		if(tmp1 != tmp2) return tmp1 < tmp2;
		if(A.x != B.x) return A.x < B.x;
		if(A.y != B.y) return A.y < B.y;
		return A.id < B.id;
	}
} a[N]; set<Node> S;
inline int ask(int x){ return x == fa[x] ? x : fa[x] = ask(fa[x]); }
inline void gao(int x){
	vis[x] = 1, b[x] = ++cnt, c[cnt] = x;
	for(int i = 0; i < (int) vec[x].size(); i++) gao(vec[x][i]);
}
namespace Seg{
	#define mid ((l + r) >> 1)
	Node s[N*30]; int lc[N*30], rc[N*30], size;
	inline void pushup(int u){
		if(!lc[u]) return (void) (s[u] = s[rc[u]]);
		if(!rc[u]) return (void) (s[u] = s[lc[u]]);
		s[u] = s[lc[u]] + s[rc[u]];
	}
	inline void ins(int &u, int l, int r, int pos){
		if(!u) u = ++cnt;
		if(l == r) return (void) (s[u] = a[l]);
		if(pos <= mid) ins(lc[u], l, mid, pos);
		else ins(rc[u], mid + 1, r, pos);
		pushup(u);
	}
	inline int merge(int x, int y){
		if(!x || !y) return x + y;
		lc[x] = merge(lc[x], lc[y]);
		rc[x] = merge(rc[x], rc[y]);
		pushup(x);
		return x;
	}
}
inline void dfs(int u){
	Seg::ins(rt[u], 1, n, b[u]);
	for(int i = 0; i < (int) g[u].size(); i++){
		dfs(g[u][i]);
		rt[u] = Seg::merge(rt[u], rt[g[u][i]]);
	}
	ans[u] = Seg::s[rt[u]].y;
}
int main(){
	read(type), read(n);
	for(int i = 2; i <= n; i++){
		read(pa[i]);
		g[pa[i]].push_back(i);
	}
	for(int i = 1; i <= n; i++)
		read(w[i]), w2[pa[i]] += w[i], fa[i] = i;
	for(int i = 1; i <= n; i++){
		a[i] = (Node){w2[i] - w[i], w2[i], i};
		S.insert(a[i]);
	}
	for(int i = 1; i <= n; i++){
		Node now = *(S.begin()); 
		S.erase(S.begin());
		int x = now.id;
		if(x == 1 || vis[pa[x]]) gao(x);
		else{
			int u = ask(pa[x]);
			S.erase(a[u]);
			a[u] = a[u] + a[x];
			S.insert(a[u]);
			vec[u].push_back(x), fa[x] = u;
		}
	}
	for(int i = 1; i <= n; i++)
		a[b[i]] = (Node){w2[i] - w[i], w2[i], i};
	dfs(1);
	for(int i = 1; i <= n; i++) 
		printf("%lld ", max(w[i], ans[i] + w[i]));
	return 0;
}
posted @ 2019-10-24 21:55  Joyemang33  阅读(495)  评论(0编辑  收藏  举报