【思维题+线段树合并】UOJ418 【集训队作业2018】三角形

【题目】
原题地址
一棵 n n n个节点的有根树,每个节点有权值 w i w_i wi。有两种操作:

  • 从手中取 w i w_i wi个石子放在 i i i节点上,此操作要求所有儿子 j j j上都有 w j w_j wj个石子
  • 将节点 i i i上的石子收回手中
    对于每个节点 i i i,为了在 i i i上放 w i w_i wi个石子,手上至少要有多少个石子。
    n ≤ 1 0 5 , w i ≤ 1 0 9 n\leq 10^5,w_i\leq 10^9 n105,wi109

【解题思路】
正着做似乎不是很好做。

考虑倒着进行这个过程,我们每次在 i i i上放 w i w_i wi个石子,或者在 i i i所有孩子都有石子时取走 i i i的石子,初始树中一个点上有石子,目标是清空树上所有石子,最小化历史最值。

观察后我们可以发现,一个点 i i i的所有孩子 j j j应该同时放上石子,接着立刻取走 i i i上的石子。

这个东西我们可以用类似线段树历史最值的方式来维护记录信息。
这个过程我们可以用二元组 ( − w i + ∑ w j , ∑ w j ) (-w_i+\sum w_j,\sum w_j) (wi+wj,wj)来表示,意义为这个过程结束后的增量和过程中历史最大值与开始时石子数的差。

接下来我们定义 ( x , y ) (x,y) (x,y)的优先级:

  • x < 0 x<0 x<0优先于 x ≥ 0 x\geq 0 x0
  • 同时满足 x < 0 x<0 x<0时, y y y小的优先
  • 同时满足 x ≥ 0 x\geq 0 x0时, y − x y-x yx大的优先

我们找出最优的一个过程,若它父亲已经完成过程,则立即执行这个过程;否则在其父亲过程完成后立即执行这个过程,那么我们需要将两个二元组合并: ( a , b ) + ( c , d ) = ( a + c , max ⁡ ( b , a + d ) ) (a,b)+(c,d)=(a+c,\max (b,a+d)) (a,b)+(c,d)=(a+c,max(b,a+d))
寻找最优的二元组我们用一个优先队列或者 set \text{set} set维护即可。

不(Y)难(Y)看(yi)出(xia),每个子树中的选择顺序是全局的子序列,于是我们对全局做一次这个过程,将全局序列求出来,然后线段树合并就可以了。

【参考代码】

#include
#define pb push_back
using namespace std;

typedef long long ll;
const int N=2e5+10;
int n,fa[N],w[N];
ll ans[N],wson[N];
vector<int>son[N];

namespace IO
{
	int read()
	{
		int ret=0;char c=getchar();
		while(!isdigit(c)) c=getchar();
		while(isdigit(c)) ret=ret*10+(c^48),c=getchar();
		return ret;
	}
	void write(ll x)
	{
		if(x>9) write(x/10);
		putchar(x%10^48);
	}
	void writesp(ll x){write(x);putchar(' ');}
}
using namespace IO;

namespace Order
{
	int ind,head[N],nex[N],pos[N],f[N];
	struct data
	{
		ll sum,mx;int id;
		data(ll _sum=0,ll _mx=0,int _id=0):sum(_sum),mx(_mx),id(_id){}
		bool operator <(const data&x)const
		{
			if((sum<0)^(x.sum<0)) return sum<x.sum;
			else if(sum>=0) return mx-sum==x.mx-x.sum?id<x.id:mx-sum>x.mx-x.sum;  
			return mx==x.mx?id<x.id:mx<x.mx;
		}
	}a[N];
	set<data>st;
	int findf(int x){return f[x]==x?x:f[x]=findf(f[x]);}
	void datamerge(int x,int y)
	{
		nex[head[y]]=x;head[y]=head[x];f[x]=y;
		if(st.find(a[y])==st.end()) return;
		st.erase(a[y]);
		a[y].sum+=a[x].sum;a[y].mx=max(a[x].mx,a[x].sum+a[y].mx);
		st.insert(a[y]);
	}
	void getorder()
	{
		for(int i=1;i<=n;++i) head[i]=f[i]=i;
		for(int i=1;i<=n;++i)
		{
			data tmp=*st.rbegin();st.erase(tmp);
			datamerge(tmp.id,findf(fa[tmp.id]));
		}
		for(int i=1,ind=n;i;i=nex[i]) pos[i]=ind--;
	}
}
using namespace Order; 

namespace Segment
{
	int rt[N];
	struct node
	{
		ll sum,mx;
		int ls,rs;
	};
	struct Segment
	{
		node t[N*100];int sz;
		void pushup(int x)
		{
			t[x].sum=t[t[x].ls].sum+t[t[x].rs].sum;
			t[x].mx=max(t[t[x].ls].mx,t[t[x].ls].sum+t[t[x].rs].mx);
		}	
		void update(int &x,int l,int r,int p,ll sum,ll mx)
		{
			if(!x) x=++sz;
			if(l==r){t[x].sum=sum;t[x].mx=mx;return;}
			int mid=(l+r)>>1;
			if(p<=mid) update(t[x].ls,l,mid,p,sum,mx);
			else update(t[x].rs,mid+1,r,p,sum,mx);
			pushup(x);
		}
		int merge(int x,int y)
		{
			if(!x || !y) return x|y;
			t[x].ls=merge(t[x].ls,t[y].ls);
			t[x].rs=merge(t[x].rs,t[y].rs);
			pushup(x);return x;
		}
	}tr;
	void dfs(int x)
	{
		tr.update(rt[x],1,n,pos[x],w[x]-wson[x],w[x]);
		for(int i=0;i<(int)son[x].size();++i)
		{
			int v=son[x][i];
			dfs(v);rt[x]=tr.merge(rt[x],rt[v]);
		}
		ans[x]=tr.t[rt[x]].mx;
	}
	void getans()
	{
		dfs(1);
		for(int i=1;i<=n;++i) writesp(ans[i]);
	}
}
using namespace Segment;

void init()
{
	read();n=read();
	for(int i=2;i<=n;++i) fa[i]=read(),son[fa[i]].pb(i);
	for(int i=1;i<=n;++i) w[i]=read(),wson[fa[i]]+=w[i];
	for(int i=1;i<=n;++i) a[i]=data(w[i]-wson[i],w[i],i),st.insert(a[i]);
}

int main()
{
#ifndef ONLINE_JUDGE
	freopen("UOJ418.in","r",stdin);
	freopen("UOJ418.out","w",stdout);
#endif
	init();getorder();getans();
	return 0;
}

你可能感兴趣的:(数据结构-线段树)