【校内模拟】攻城略池(启发式合并)(树形DP)(二分答案)

29 篇文章 0 订阅
23 篇文章 0 订阅

题解:

发现实际上把每个儿子第一次到达祖先的时间点拿出来排个序,祖先单位时间受到的伤害其实是个分段一次函数。总伤害就是求前缀和。

需要维护排好序的儿子到祖先的时间,dsu on tree,每次计算的时候二分答案并check,没了。。。


代码:

#include<bits/stdc++.h>
#define ll long long
#define re register
#define cs const

namespace IO{
	inline char gc(){
		static cs int Rlen=1<<22|1;
		static char buf[Rlen],*p1,*p2;
		return (p1==p2)&&(p2=(p1=buf)+fread(buf,1,Rlen,stdin),p1==p2)?EOF:*p1++;
	}
	
	template<typename T>
	inline T get(){
		char c;T num;
		while(!isdigit(c=gc()));num=c^48;
		while(isdigit(c=gc()))num=(num+(num<<2)<<1)+(c^48);
		return num;
	}
	inline int gi(){return get<int>();}
}
using namespace IO;

using std::cerr;
using std::cout;
typedef std::pair<ll,int> pli;
#define fi first
#define se second

cs int N=1e5+7;

int lc[N],rc[N],siz[N],tot;
ll sum[N],val[N];

int del[N],tp;
int q[N],qn;

inline int newnode(int v){
	int u=tp?del[tp--]:++tot;
	lc[u]=rc[u]=0;
	siz[u]=1,sum[u]=val[u]=v;
	return u;
}

inline void pushup(int u){
	siz[u]=siz[lc[u]]+siz[rc[u]]+1;
	sum[u]=sum[lc[u]]+sum[rc[u]]+val[u];
}

inline void Zig(int &u){
	int v=lc[u];lc[u]=rc[v];rc[v]=u;
	pushup(u),pushup(v);u=v;
}

inline void Zag(int &u){
	int v=rc[u];rc[u]=lc[v];lc[v]=u;
	pushup(u),pushup(v);u=v;
}
cs double alpha=0.65;
void ins(int &u,int v){
	if(!u){u=newnode(v);return ;}
	siz[u]++;sum[u]+=v;
	if(v<=val[u]){ins(lc[u],v);if(siz[lc[u]]>=siz[u]*alpha)Zig(u);}
	else {ins(rc[u],v);if(siz[rc[u]]>=siz[u]*alpha)Zag(u);}
}

void inorder_dfs(int u){
	if(lc[u])inorder_dfs(lc[u]);
	q[++qn]=val[u];del[++tp]=u;
	if(rc[u])inorder_dfs(rc[u]);
}

ll query(int u,int v){
	if(!u)return 0;
	if(val[u]>v)return query(lc[u],v);
	else return (siz[lc[u]]+1)*(ll)v-sum[lc[u]]-val[u]+query(rc[u],v);
}

int n,d[N];
int el[N],nxt[N<<1],to[N<<1],w[N<<1],ect;
inline void adde(int u,int v,int val){
	nxt[++ect]=el[u],el[u]=ect,to[ect]=v,w[ect]=val;
	nxt[++ect]=el[v],el[v]=ect,to[ect]=u,w[ect]=val;
}

int sz[N],son[N],to_son[N];
void dfs1(int u,int p){
	sz[u]=1;for(int re e=el[u],v=to[e];e;v=to[e=nxt[e]])
	if(v!=p){dfs1(v,u),sz[u]+=sz[v];if(sz[v]>sz[son[u]])son[u]=v,to_son[u]=w[e];}
}

int rt[N],tag[N],Ans;

inline void Join(int u,int v,int w){
//由于已经轻重链剖分,直接将v并到u上同时处理标记
//不然还要启发式合并考虑交换两个根。。。感觉标记处理要分类讨论,不想分类讨论QAQ 
	qn=0;inorder_dfs(rt[v]);w+=tag[v]-tag[u];
	for(int re i=1;i<=qn;++i)ins(rt[u],q[i]+w); 
}

inline bool check(int u,int t){
	return query(rt[u],t)>=d[u];
}

inline void solve(int u){
	if(!rt[u])ins(rt[u],0);
	else {
		int l=-tag[u]-7,r=1e8+7,mid,ans=0;//下界必须足够低,因为最后答案加上tag[u],当 d[u] 过小的时候这个二分就gg 
		while(l<=r)check(u,mid=(l+r)/2)?r=mid-1,ans=mid:l=mid+1;
	//	cerr<<"solve : "<<u<<" "<<ans+tag[u]<<"\n"; 
		Ans=std::max(Ans,ans+tag[u]);ins(rt[u],ans);
	}
}

void dfs2(int u,int p){
	if(son[u])dfs2(son[u],u),rt[u]=rt[son[u]],tag[u]=tag[son[u]]+to_son[u];
	for(int re e=el[u],v=to[e];e;v=to[e=nxt[e]])
	if(v!=p&&v!=son[u]){dfs2(v,u);Join(u,v,w[e]);}
	solve(u);
}

signed main(){
#ifdef zxyoi
	freopen("conquer.in","r",stdin);
#else
#ifndef ONLINE_JUDGE
	freopen("conquer.in","r",stdin);freopen("conquer.out","w",stdout);
#endif
#endif
	n=gi();
	for(int re i=1;i<=n;++i)d[i]=gi();
	for(int re i=1;i<n;++i){
		int u=gi(),v=gi(),w=gi();
		adde(u,v,w);
	}
	dfs1(1,0);dfs2(1,0);cout<<Ans<<"\n";
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值