P7889 「MCOI-06」Eert Tuc Knil 题解

Description

传送门

Solution

算法一

对于每次询问跑一遍 dp \text{dp} dp 计算答案即可。期望得分 5 5 5 分。

void dfs(int now,int fath){
	for (int i=head[now];i;i=e[i].nxt){
		int y=e[i].to;
		if (y==fath)  dfs(y,now);
		dp[now]+=max(dp[y],0);
	}
	dp[now]+=a[now];
}

算法二

为方便叙述,令所有节点的点权增量为 △ \triangle w u w_u wu 为节点 u u u 的权值, m m m 为所有输入的数的最大值。

观察算法一的 dp \text{dp} dp 式,不难发现: 若节点 v v v 会对其祖先节点 f u f_u fu 产生 w v w_v wv 的贡献,当且仅当从 u u u v v v 的祖先且 u u u v v v 的路径上各个点的 f f f 值均不小于 0 0 0

注意到,节点权值虽然会因为 △ \triangle 的变化而变化,但 f f f 值始终是随着 △ \triangle 的变大而变大的,所以我们可以定义状态 g u g_u gu,表示满足 f u ≥ 0 f_u \ge 0 fu0 的最小 △ \triangle

先思考如何求出 g u g_u gu

不难想到二分,令当前二分的值为 δ \delta δ。根据上述性质, δ \delta δ 合法当且仅当

0 ≤ w u + ∑ v ∈ subtree ( u ) , u ≠ v [ max ⁡ p ∈ path ( u , v ] { g p } ≤ δ ] × ( w v + δ ) 0 \le w_u+\sum_{v \in \text{subtree}(u),u \neq v} \left[\max_{p \in \text{path}(u,v]} \{g_p\} \le \delta\right] \times (w_v + \delta) 0wu+vsubtree(u),u=v[ppath(u,v]max{gp}δ]×(wv+δ)

该如何快速求出上面的值呢?考虑线段树合并+线段树二分。具体来说,线段树上下标为 x x x 的位置记录了满足 max ⁡ p ∈ path ( u , v ] { g p } = x \max_{p \in \text{path}(u,v]} \{g_p\}=x maxppath(u,v]{gp}=x 的节点 v v v 所对应的 w v w_v wv 之和以及这样的 v v v 的数量。那么为求出 g u g_u gu,我们可以先将各个儿子的权值线段树合并在一起,然后在线段树上二分并在递归的同时维护一段前缀的信息。最后我们更新线段树,即将所有 [ − m , g u ) [-m,g_u) [m,gu) 累加到 g u g_u gu 处并清空 [ − m , g u ) [-m,g_u) [m,gu)

从而,我们 O ( n log ⁡ m ) O(n \log m) O(nlogm) 地求出了 g g g。那么该如何计算答案呢?

其实,计算答案与预处理 g g g 的唯一区别在于固定了 δ \delta δ。显然,直接做前缀查询就可以搞定了。不过,在合并过程中 u u u 处的权值线段树已经消匿了,因此我们需要对询问离线。

这样一来,总时空复杂度为 O ( n log ⁡ m ) O(n \log m) O(nlogm)。期望得分 [ 77 , 100 ] [77,100] [77,100] 分。

Code

被卡常了,喷出题人。因此下面是 77 77 77 分的代码。。。

#include <bits/stdc++.h>
#define ll long long
#define PA pair<int,long long>
#define fi first
#define se second
#define MP make_pair
using namespace std;
const int maxl=1000005,maxg=80;

int read(){
	int s=0,w=1;char ch=getchar();
	while (ch<'0'||ch>'9'){if (ch=='-')  w=-w;ch=getchar();}
	while (ch>='0'&&ch<='9'){s=(s<<1)+(s<<3)+(ch^'0');ch=getchar();}
	return s*w;
}
int n,m,q,tot,cnt;
int head[maxl],w[maxl],t[maxl],root[maxl],deg[maxl];ll ans[maxl];
vector<PA > ve[maxl];

struct edge{int nxt,to;}e[maxl];
struct Segment_tree{int lson,rson,tag,cnt;ll sum;}tree[maxl*maxg];

namespace ST{
	bool is_leaf(int x){return (!(tree[x].lson|tree[x].rson));}
	void pushup(int rt){
		int ls=tree[rt].lson,rs=tree[rt].rson;
		tree[rt].cnt=tree[ls].cnt+tree[rs].cnt;
		tree[rt].sum=tree[ls].sum+tree[rs].sum;
	}
	int Clone(int rt){
		if (!rt)  rt=++tot;
		return rt;
	}
	void F(int rt){tree[rt].cnt=tree[rt].sum=0,tree[rt].tag=1;}
	void pushdown(int rt){
		tree[rt].lson=Clone(tree[rt].lson);
		tree[rt].rson=Clone(tree[rt].rson);
		if (tree[rt].tag){
			int ls=tree[rt].lson,rs=tree[rt].rson;
			F(ls),F(rs),tree[rt].tag=0;
		}
	}
	void Pushdown(int rt){
		if (tree[rt].tag){
			F(tree[rt].lson),F(tree[rt].rson);
			tree[rt].tag=0; 
		}
	}
	int change(int nl,int l,int r,int rt,PA del){
		if (!rt)  rt=++tot;
		if (l==r){
			tree[rt].cnt+=del.fi;
			tree[rt].sum+=del.se;
			return rt;
		}
		pushdown(rt);
		
		int mid=(l+r)>>1;
		if (nl<=mid)  tree[rt].lson=change(nl,l,mid,tree[rt].lson,del);
		else tree[rt].rson=change(nl,mid+1,r,tree[rt].rson,del);
		pushup(rt);
		
		return rt;
	}
	int assign(int nl,int nr,int l,int r,int rt){
		if (!rt)  rt=++tot;
		if (nl<=l&&r<=nr){
			tree[rt].cnt=tree[rt].sum=0,tree[rt].tag=1;
			return rt;
		}
		pushdown(rt);
		
		int mid=(l+r)>>1;
		if (nl<=mid)  tree[rt].lson=assign(nl,nr,l,mid,tree[rt].lson);
		if (nr>mid)  tree[rt].rson=assign(nl,nr,mid+1,r,tree[rt].rson);
		pushup(rt);
		
		return rt;
	}
	PA query(int nl,int nr,int l,int r,int rt){
		if (nl<=l&&r<=nr)  return MP(tree[rt].cnt,tree[rt].sum);
		Pushdown(rt);
		
		int mid=(l+r)>>1;PA res;
		res.fi=res.se=0;
		if (tree[rt].lson&&nl<=mid)  res=query(nl,nr,l,mid,tree[rt].lson);
		if (tree[rt].rson&&nr>mid){
			PA tmp=query(nl,nr,mid+1,r,tree[rt].rson);
			res.fi+=tmp.fi,res.se+=tmp.se;
		}
		return res;
	}
	int Merge(int x,int y,int l,int r){
		if (is_leaf(x))  swap(x,y);
		if (is_leaf(y)){
			tree[x].cnt+=tree[y].cnt;
			tree[x].sum+=tree[y].sum;
			return x;
		}
		pushdown(x),pushdown(y);
	
		int mid=(l+r)>>1;
		tree[x].lson=Merge(tree[x].lson,tree[y].lson,l,mid);
		tree[x].rson=Merge(tree[x].rson,tree[y].rson,mid+1,r);
		pushup(x);
		
		return x;
	}
	int work(int pos){
		int now=root[pos],l=-m,r=m,res=0;
		ll precnt=0,presum=0;
		while (l<r){
			int mid=(l+r)>>1,ls=tree[now].lson,rs=tree[now].rson;
			pushdown(now);
			ll cntl=tree[ls].cnt+precnt,suml=tree[ls].sum+presum;
		
			if ((cntl+1)*mid+suml+w[pos]>=0) res=mid,now=ls,r=mid;
			else now=rs,l=mid+1,precnt+=tree[ls].cnt,presum+=tree[ls].sum;
		}
		return res;
	}
}
namespace ducati{
	void add_edge(int u,int v){
		cnt++;
		e[cnt].to=v,e[cnt].nxt=head[u],head[u]=cnt;
	}
	void get_all_in(){
		n=read(),q=read();
		for (int i=2;i<=n;i++){
			int fa=read();
			add_edge(fa,i),deg[fa]++;
		}
		for (int i=1;i<=n;i++)  w[i]=read(),m=max(m,abs(w[i]));
		for (int i=1;i<=q;i++){
			int u=read(),x=read();
			m=max(m,abs(x)),ve[u].push_back(MP(i,x));
		}
		m++;
	}
	void dfs(int now,int fath){
		if (!deg[now]){
			t[now]=-w[now];
			root[now]=ST::change(t[now],-m,m,root[now],MP(1,w[now]));
			for (int i=0;i<ve[now].size();i++){
				int id=ve[now][i].fi,delta=ve[now][i].se;
				ans[id]=w[now]+delta;
			}
			return;
		}
		int cnt_son=0;
		for (int i=head[now];i;i=e[i].nxt){
			int y=e[i].to;
			if (y==fath)  continue;
			dfs(y,now);
			
			if (cnt_son)  root[now]=ST::Merge(root[now],root[y],-m,m);
			else root[now]=root[y];
			cnt_son++;
		}
		t[now]=ST::work(now);
		for (int i=0;i<ve[now].size();i++){
			int id=ve[now][i].fi,delta=ve[now][i].se;
			PA sumv=ST::query(-m,delta,-m,m,root[now]);
			
			ll res=(ll)sumv.fi*delta+sumv.se+w[now]+delta;
			ans[id]=res;
		}
		PA tmp;
		tmp=ST::query(-m,t[now]-1,-m,m,root[now]);
		root[now]=ST::assign(-m,t[now]-1,-m,m,root[now]);
		tmp.fi++,tmp.se+=w[now];
		root[now]=ST::change(t[now],-m,m,root[now],tmp);
	}
	void print(){
		for (int i=1;i<=q;i++)  printf("%lld\n",ans[i]);
	}
	void solve(){get_all_in(),dfs(1,0),print();}
}

signed main(){
	ducati::solve();
	return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值