[集训队作业2018] 三角形(贪心,堆,线段树合并)

传送门

首先,在结点 u u u放上 w [ u ] w[u] w[u]个石子后,出于贪心考虑,下一步一定会把 u u u的所有儿子 v v v上的石子收回手中。

转换题意:
c n t cnt cnt为当下树上的石子数,对每个结点 u u u可以执行一次操作:

  • step1. c n t + = w [ u ] cnt+=w[u] cnt+=w[u]
  • step2. c n t − = ∑ v ∈ s o n ( u ) w [ v ] cnt-=\sum_{v\in son(u)}w[v] cnt=vson(u)w[v]

当且仅当对 u u u的所有儿子 v v v都执行过操作,才能对 u u u执行操作。
问历史上 c n t cnt cnt的最大值最小可以是多少?

先不考虑 儿子都操作完父亲才能操作的限制,思考一个简单版问题:有 n n n个操作 ( a [ i ] , b [ i ] ) (a[i],b[i]) (a[i],b[i]),其含义为:先令 c n t + = a [ i ] cnt+=a[i] cnt+=a[i],再令 c n t + = b [ i ] cnt+=b[i] cnt+=b[i]。对这 n n n个操作排序,使历史上 c n t cnt cnt的最大值最小。( a [ i ] = w [ i ] , b [ i ] = − ∑ w [ v ] a[i]=w[i],b[i]=-\sum w[v] a[i]=w[i],b[i]=w[v]

对于一段操作 i , i + 1 , . . . , j i,i+1,...,j i,i+1,...,j,设经过这段操作后 c n t = c n t + d e l t a cnt=cnt+delta cnt=cnt+delta,且执行这段操作途中历史上 c n t cnt cnt的最大值为 c n t + m x cnt+mx cnt+mx

那么每次向后添加一个二元组 ( a [ j + 1 ] = w [ j + 1 ] , b [ j + 1 ] = − ∑ w [ v ] ) (a[j+1]=w[j+1],b[j+1]=-\sum w[v]) (a[j+1]=w[j+1],b[j+1]=w[v])
就相当于 d e l t a ← d e l t a + w [ j + 1 ] − ∑ w [ v ] , m x ← m a x ( m x , m x + w [ j + 1 ] ) delta\leftarrow delta+w[j+1]-\sum w[v],mx\leftarrow max(mx,mx+w[j+1]) deltadelta+w[j+1]w[v],mxmax(mx,mx+w[j+1])

所以我们换一种方式,用二元组 ( w [ i ] − ∑ w [ v ] , w [ i ] ) (w[i]-\sum w[v],w[i]) (w[i]w[v],w[i])来描述操作 i i i
(相当于操作段 i i i ( d e l t a , m x ) (delta,mx) (delta,mx)

由上面可以发现这个二元组是可以简易复合的。我们的问题就是给这些二元组安排一个优先级,使得从左到右复合之后 m x mx mx最小。

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

因为复合顺序改变 d e l t a delta delta不变,所以只需考虑复合后的 m x mx mx,显然 m x mx mx 越小越好

  • 对于 d e l t a delta delta为负的二元组,一定比 d e l t a delta delta为正的二元组优。
  • 对于 d e l t a delta delta都为负的二元组, m x mx mx越小的优先级越高。
  • 对于 d e l t a delta delta都为正的二元组, d e l t a − m x delta−mx deltamx越小的优先级越高。

然后我们就证明了不存在二元组 A , B , C A,B,C A,B,C满足 A < B , B < C , C < A A<B,B<C,C<A A<B,B<C,C<A,即可以按照这个优先级给二元组排序,且这样最小化最终的 m x mx mx
这样就解决了简化问题。

考虑原问题。
u u u的所有儿子 v v v都执行过操作才能对 u u u执行操作 这个限制非常麻烦,考虑反转:
对每个点 u u u可以执行一次操作:

  • step1. 把 ∑ v ∈ s o n ( u ) w [ v ] \sum_{v\in son(u)}w[v] vson(u)w[v]接到序列 c c c的最后面
  • step2. 把 − w [ u ] -w[u] w[u]接到序列 c c c的最后面

u u u的父亲 f f f执行过操作才能对 u u u执行操作。
问序列 c c c后缀和的最大值最小可以是多少。
(反转前相当于每个操作先把 w [ u ] w[u] w[u]接到 c c c的最后面,再把 − ∑ w [ v ] -\sum w[v] w[v]接到 c c c的最后面,求最终的 c c c序列的前缀和的最大值最小可以是多少)

转化后 u u u点的操作对应的二元组为 ( ∑ w [ v ] − w [ u ] , ∑ w [ v ] ) (\sum w[v]-w[u],\sum w[v]) (w[v]w[u],w[v])

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

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

#include<iostream>
#include<cstdio>
#include<vector>
#include<set>
using namespace std;
typedef long long ll;
const int inf=0x3f3f3f3f;
const int N=2e5+10;
int T,n,par[N],fa[N];
int vis[N],b[N],c[N],tim;
int rt[N];
ll w[N],sw[N],ans[N];
vector<int> g[N],vec[N];
struct Node{
    
    
	ll delta,mx;int id;
	friend Node operator + (Node a,Node b){
    
    
		return (Node){
    
    a.delta+b.delta,max(a.mx,a.delta+b.mx),a.id};
	}
	friend bool operator < (Node a,Node b){
    
    
		ll tmp1=max(a.mx,a.delta+b.mx);
		ll tmp2=max(b.mx,b.delta+a.mx);
		if(tmp1!=tmp2) return tmp1<tmp2;
		if(a.delta!=b.delta) return a.delta<b.delta;
		if(a.mx!=b.mx) return a.mx<b.mx;
		return a.id<b.id;
	}
}a[N];
set<Node> s;
int find(int x){
    
    
	if(x==fa[x]) return x;
	return fa[x]=find(fa[x]);
}
void init(int x){
    
    
	vis[x]=1;
	b[x]=++tim;c[tim]=x;
	for(int i=0;i<vec[x].size();i++) init(vec[x][i]);
}
namespace Seg{
    
    
	int ls[N*30],rs[N*30],tot;
	Node tr[N*30];
	void pushup(int u){
    
    
		if(!ls[u]){
    
    tr[u]=tr[rs[u]];return;}
		if(!rs[u]){
    
    tr[u]=tr[ls[u]];return;}
		tr[u]=tr[ls[u]]+tr[rs[u]];
	}
	void insert(int &u,int l,int r,int pos){
    
    
		if(!u) u=++tot;
		if(l==r){
    
    
			tr[u]=a[l];
			return;
		}
		int mid=(l+r)>>1;
		if(pos<=mid) insert(ls[u],l,mid,pos);
		else insert(rs[u],mid+1,r,pos);
		pushup(u);
	}
	int merge(int x,int y){
    
    
		if(!x||!y) return x+y;
		ls[x]=merge(ls[x],ls[y]);
		rs[x]=merge(rs[x],rs[y]);
		pushup(x);
		return x;
	}
}
void dfs(int u){
    
    
	Seg::insert(rt[u],1,n,b[u]);
	for(int i=0;i<g[u].size();i++){
    
    
		int v=g[u][i];
		dfs(v);
		rt[u]=Seg::merge(rt[u],rt[v]);
	}
	ans[u]=Seg::tr[rt[u]].mx;
}
int main(){
    
    
	scanf("%d",&T);
	scanf("%d",&n);
	for(int i=2;i<=n;i++){
    
    
		scanf("%d",&par[i]);
		g[par[i]].push_back(i);
	}
	for(int i=1;i<=n;i++){
    
    
		scanf("%lld",&w[i]);
		sw[par[i]]+=w[i];
		fa[i]=i;
	}
	for(int i=1;i<=n;i++){
    
    
		a[i]=(Node){
    
    sw[i]-w[i],sw[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[par[x]]) init(x);
		else{
    
    
			int fx=find(par[x]);
			s.erase(a[fx]);
			a[fx]=a[fx]+a[x];
			s.insert(a[fx]);
			vec[fx].push_back(x),fa[x]=fx;
		}
	}
	for(int i=1;i<=n;i++){
    
    
		a[b[i]]=(Node){
    
    sw[i]-w[i],sw[i],i};
	}
	dfs(1);
	for(int i=1;i<=n;i++)
		printf("%lld ",max(w[i],ans[i]+w[i]));
	return 0;
}

参考文章:
https://www.cnblogs.com/mangoyang/p/11735219.html

猜你喜欢

转载自blog.csdn.net/Emma2oo6/article/details/120732339