bzoj 4515: [Sdoi2016]游戏 树链剖分

       首先可以把修改转换一下,因为那个dis非常不爽。显然s~t的路径有s~lca和lca~t组成。令d[x]表示x的深度,对于s~lca上面的点,修改的值相当于a*(d[s]-d[x])+b=-a*d[x]+(b+a*d[s]),lca~t上面的点的值相当于a*(d[s]+d[x]-2*d[lca])+b=a*d[x]+(b+a*(d[s]-d[lca]*2)),这样就可以得到新的a'和b',然后一个点的值就相当于a'*d[x]+b'了,这样就只和a',b'和d[x]有关了。

       注意到在树链剖分对应的线段树区间中,相连的一部分它们在树种也是相连的,这样就满足d[]单调递增,观察a'*d[x]+b',发现这相当于直线f(x)=a'x+b在d[x](两个x不是一个意思)处的取值!那么就相当于用线段树维护一个区间中的若干条直线在每个部分的最小值。

       不妨考虑标记永久化(这里只是一定程度上的永久化但还是要下传的)。让线段树中的一个节点只对应一条直线,那么如果在这个区间加入一条直线怎么办呢?要分类讨论,设新加入的f1(x)=k1x+b1,原来的f2(x)=k2x+b2,左端点为l,右端点为r,那么有:

       1.f1(d[l])<f2(d[l])且f1(d[r])<f2(d[r]),对应一条直线在两个端点都比另一条小,那么显然在l~r中f1(x)处处比f2(x)小,直接把f2(x)替换为f1(x);

       2.同理若上式的两个符号都为>,那么f1(x)处处不如f2(x)优,不做更改。

       3.k1<k2,那么由于不满足1.2,显然两条直线有交点,此时解不等式f1(x)<f2(x)得到x>(b1-b2)/(k2-k1),那么判断(b1-b2)/(k2-k1)在左半区间还是右半区间递归下传即可;

       4.k1>k2同理。

       实际上这就是线段树维护半平面交的过程~~~~~

       询问就简单多了,直接用标记永久化的线段树的方法更新即可。

AC代码如下:

#include<iostream>
#include<cstdio>
#include<cstring>
#define ll long long
#define N 100005
#define inf 123456789123456789LL
using namespace std;

int n,m,tot,dfsclk,fst[N],pnt[N<<1],len[N<<1],nxt[N<<1],fa[N],son[N],sz[N],anc[N],pos[N],id[N];
int lk[N<<2]; ll lb[N<<2],val[N<<2],d[N],ans; bool tag[N<<2];
int read(){
	int x=0,fu=1; char ch=getchar();
	while (ch<'0' || ch>'9'){ if (ch=='-') fu=-1; ch=getchar(); }
	while (ch>='0' && ch<='9'){ x=x*10+ch-'0'; ch=getchar(); }
	return x*fu;
}
void add(int x,int y,int z){
	pnt[++tot]=y; len[tot]=z; nxt[tot]=fst[x]; fst[x]=tot;
}
void dfs(int x){
	int p; sz[x]=1;
	for (p=fst[x]; p; p=nxt[p]){
		int y=pnt[p];
		if (fa[x]!=y){
			fa[y]=x; d[y]=d[x]+len[p];
			dfs(y); sz[x]+=sz[y];
			if (sz[y]>sz[son[x]]) son[x]=y;
		}
	}
}
void divide(int x,int tp){
	pos[x]=++dfsclk; id[dfsclk]=x; anc[x]=tp;
	if (son[x]) divide(son[x],tp); int p;
	for (p=fst[x]; p; p=nxt[p]){
		int y=pnt[p];
		if (y!=fa[x] && y!=son[x]) divide(y,y);
	}
}
int lca(int x,int y){
	for (; anc[x]!=anc[y]; x=fa[anc[x]])
		if (d[anc[x]]<d[anc[y]]) swap(x,y);
	return (d[x]<d[y])?x:y;
}
void maintain(int k,int l,int r){
	if (l<r) val[k]=min(val[k<<1],val[k<<1|1]); else val[k]=inf;
	if (tag[k]) val[k]=min(val[k],min(d[id[l]]*lk[k],d[id[r]]*lk[k])+lb[k]);
}
void build(int k,int l,int r){
	val[k]=inf;
	if (l<r){
		int mid=(l+r)>>1; build(k<<1,l,mid); build(k<<1|1,mid+1,r);
	}
}
void up(int k,int l,int r,int u,ll v){
	if (!tag[k]){ tag[k]=1; lk[k]=u; lb[k]=v; } else{
		ll x1=d[id[l]]*u+v,y1=d[id[r]]*u+v,x2=d[id[l]]*lk[k]+lb[k],y2=d[id[r]]*lk[k]+lb[k];
		int mid=(l+r)>>1;
		if (x1<=x2 && y1<=y2){
			lk[k]=u; lb[k]=v;
		} else if (x1>=x2 && y1>=y2)	 return;
		else if (u<lk[k]){
			ll tmp=(v-lb[k])/(lk[k]-u)+1;
			if (tmp<=d[id[mid]]){
				swap(u,lk[k]); swap(v,lb[k]);
				up(k<<1,l,mid,u,v);
			} else up(k<<1|1,mid+1,r,u,v);
		} else{
			ll tmp=(lb[k]-v-1)/(u-lk[k]);
			if (tmp>d[id[mid]]){
				swap(u,lk[k]); swap(v,lb[k]);
				up(k<<1|1,mid+1,r,u,v);
			} else up(k<<1,l,mid,u,v);
		}
	}
	maintain(k,l,r);
}
void ins(int k,int l,int r,int x,int y,int u,ll v){
	if (l==x && r==y){ up(k,l,r,u,v); return; }
	int mid=(l+r)>>1;
	if (y<=mid) ins(k<<1,l,mid,x,y,u,v); else
	if (x>mid) ins(k<<1|1,mid+1,r,x,y,u,v); else{
		ins(k<<1,l,mid,x,mid,u,v); ins(k<<1|1,mid+1,r,mid+1,y,u,v);
	}
	maintain(k,l,r);
}
void qry(int k,int l,int r,int x,int y){
	if (l==x && r==y){ ans=min(ans,val[k]); return; }
	if (tag[k]) ans=min(ans,min(d[id[x]]*lk[k],d[id[y]]*lk[k])+lb[k]);
	int mid=(l+r)>>1;
	if (y<=mid) qry(k<<1,l,mid,x,y); else
	if (x>mid) qry(k<<1|1,mid+1,r,x,y); else{
		qry(k<<1,l,mid,x,mid); qry(k<<1|1,mid+1,r,mid+1,y);
	}
}
int main(){
	n=read(); m=read(); int i,x,y,z;
	for (i=1; i<n; i++){
		x=read(); y=read(); z=read();
		add(x,y,z); add(y,x,z);
	}
	dfs(1); divide(1,1); build(1,1,n); int k,u,v; ll tmp;
	while (m--){
		k=read();
		if (k==1){
			x=read(); y=read(); u=read(); v=read(); z=lca(x,y);
			tmp=d[x]*u+v;
			for (; anc[x]!=anc[z]; x=fa[anc[x]])
				ins(1,1,n,pos[anc[x]],pos[x],-u,tmp);
			ins(1,1,n,pos[z],pos[x],-u,tmp);
			tmp-=(d[z]<<1)*u;
			for (; anc[y]!=anc[z]; y=fa[anc[y]])
				ins(1,1,n,pos[anc[y]],pos[y],u,tmp);
			ins(1,1,n,pos[z],pos[y],u,tmp);
		} else{
			x=read(); y=read(); ans=inf;
			for (; anc[x]!=anc[y]; x=fa[anc[x]]){
				if (d[anc[x]]<d[anc[y]]) swap(x,y);
				qry(1,1,n,pos[anc[x]],pos[x]);
			}
			if (d[x]>d[y]) swap(x,y); qry(1,1,n,pos[x],pos[y]);
			printf("%lld\n",ans);
		}
	}
	return 0;
}


by lych

2016.4.15

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值