[JZOJ6359] 【NOIP2019模拟2019.9.15】小ω的树

8 篇文章 0 订阅
2 篇文章 0 订阅

题目

题目大意

给你一棵树,带点权和边权。
要你选择一个联通子图,使得点权和乘最小边权最大。
支持修改点权操作。


思考历程

显然,最先想到的当然是重构树了……
重构树就是在做最大生成树的时候,当两个联通块相连时,新增一个点,将两个联通块的根节点连上去。
这个新建的点上记录这条边的边权,那么以它为子树的答案就是子树的点权和乘上自己表示的这条边的边权。

然后题目就变成了一个似乎很经典的问题:给你 a i a_i ai b i b_i bi,每次修改可以将区间内的 a i a_i ai区间加,询问最大的 a i b i a_ib_i aibi……
然而由于我智力低下,完全不会维护啊……
于是就暴力维护了。


正解

其实可以分块……
这可以看做一堆一次函数取最大值。
如果只是一条链,直接分成 n \sqrt n n 块,每个块里维护一个凸壳。
每个块有个 t a g tag tag标记,表示当前块内的答案 a n s ans ans x = t a g x=tag x=tag和块内的一次函数交点的纵坐标最大值。
那么修改的时候整块就直接改 t a g tag tag,然后在凸壳上二分出 a n s ans ans。散块就暴力重构。

对于一棵树,当然是树链剖分了。设某条重链的长度为 L L L,则将重链分成 L \sqrt L L 块,然后一模一样地搞。
这样时间复杂度是否有保障呢?
首先,对于一条重链,它自己的时间复杂度就是 L lg ⁡ L \sqrt L \lg L L lgL
既然有好多条重链,是不是意味着再乘个 lg ⁡ \lg lg
然而不是这样。
树链剖分有个性质:轻儿子的大小小于整棵树大小的一半。
所以从上到下,每个重链的链顶子树的大小是一直在减半的。
设子树大小为 S S S。显然 L lg ⁡ L ≤ S lg ⁡ S \sqrt L \lg L\leq \sqrt S \lg S L lgLS lgS
所以时间复杂度就是 S lg ⁡ S + S 2 lg ⁡ S 2 + S 4 lg ⁡ S 4 + . . . \sqrt S \lg S+\sqrt \frac{S}{2} \lg \frac{S}{2}+\sqrt \frac{S}{4} \lg \frac{S}{4}+... S lgS+2S lg2S+4S lg4S+...
lg ⁡ \lg lg比较小,所以看成同样的。提出来,就变成了 S + S 2 + S 4 + . . . = O ( S ) \sqrt S+\sqrt \frac{S}{2}+\sqrt \frac{S}{4}+...=O(\sqrt S) S +2S +4S +...=O(S )
所以单次操作时间复杂度居然是 O ( n lg ⁡ n ) O(\sqrt n\lg n) O(n lgn)
所以这是能够过的……
不过记得要卡一卡常数。

另外有个强大的集训队大佬提供的方法:定期重构。
也就是做几个询问重构一次。
设做 B B B个询问重构一次。
对于询问的点,建一棵虚树。显然虚树的点有 2 B − 1 2B-1 2B1个。建完之后对于虚树上的每一条边维护一个凸壳,记录一个 t a g tag tag,求答案的时候在凸壳上二分。
计算询问的时候,直接暴力沿着边打 t a g tag tag。虚树的边有 2 B − 2 2B-2 2B2个,所以一次修改时间就是 O ( B lg ⁡ n ) O(B \lg n) O(Blgn)
所以总的时间就是: O ( n m B lg ⁡ n + m B lg ⁡ n ) O(n\frac{m}{B}\lg n+mB\lg n) O(nBmlgn+mBlgn)
平衡规划得 B = n B=\sqrt n B=n


代码

树链剖分:

using namespace std;
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#define N 300010
#define ll long long
inline int input(){
	char ch=getchar();
	while (ch<'0' || '9'<ch)
		ch=getchar();
	int x=0;
	do{
		x=x*10+ch-'0';
		ch=getchar();
	}
	while ('0'<=ch && ch<='9');
	return x;
}
int n,m;
int a[N];
struct edge{
	int x,y;
	ll v;
} ed[N];
inline bool cmped(const edge &a,const edge &b){return a.v>b.v;}
int cnt,fa[N*2];
int ufs[N*2];
inline int get(int x){
	if (ufs[x]==x)
		return x;
	return ufs[x]=get(ufs[x]);
}
struct EDGE{
	int to;
	EDGE *las;
} e[N*2];
int ne;
EDGE *last[N*2];
ll sum[N*2],mn[N*2],p[N*2],q[N*2];
int siz[N*2],hs[N*2],top[N*2],dfn[N*2],nowdfn,lis[N*2],len[N*2];
void dfs1(int x){
	siz[x]=1;
	for (EDGE *ei=last[x];ei;ei=ei->las){
		dfs1(ei->to);
		sum[x]+=sum[ei->to];
		siz[x]+=siz[ei->to];
		if (siz[ei->to]>siz[hs[x]])
			hs[x]=ei->to;
	}
}
void dfs2(int x,int t){
	top[x]=t;
	len[top[x]]++;
	dfn[x]=++nowdfn;
	lis[nowdfn]=x;
	if (hs[x])
		dfs2(hs[x],t);
	for (EDGE *ei=last[x];ei;ei=ei->las)
		if (ei->to!=hs[x])
			dfs2(ei->to,ei->to);
}
int bel[N*2],nb,L[N*2],R[N*2];
int st[N*2],*h[N*2],*t[N*2];
ll tag[N*2];
ll ans[N*2];
ll h1[20000000],h2[20000000],nh1,nh2;
inline void recover(int b){
	for (int i=L[b];i<=R[b];++i)
		q[lis[i]]+=p[lis[i]]*tag[b];
	tag[b]=0;
}
int tmp[N*2];
inline bool cmpt(int a,int b){return p[a]<p[b] || p[a]==p[b] && q[a]>q[b];}
inline bool ok(int a,int b,int x){return 0>=x*(p[a]-p[b])+q[a]-q[b];}
inline void getans(int b){
	int *l=h[b]+1,*r=t[b],*res=h[b];
	while (l<=r){
		int *mid=l+(r-l>>1);
		if (ok(*(mid-1),*mid,tag[b]))
			l=(res=mid)+1;
		else
			r=mid-1;
	}
	ans[b]=p[*res]*tag[b]+q[*res];
}
inline bool judge(int i,int j,int k){
	return (q[k]-q[j])*(p[j]-p[i])>=(q[i]-q[j])*(p[j]-p[k]);
}
inline void build(int b){
	int k=0;
	for (int i=L[b];i<=R[b];++i)
		tmp[k++]=lis[i];
	sort(tmp,tmp+k,cmpt);
	*h[b]=tmp[0];
	t[b]=h[b];
	for (int i=1;i<k;++i)
		if (p[*t[b]]!=p[tmp[i]]){ 
			while (h[b]<t[b] && judge(*(t[b]-1),*t[b],tmp[i]))
				t[b]--; 
			*(++t[b])=tmp[i];
		} 
	getans(b);
}
inline void add(int l,int r,int c){
	if (bel[l]==bel[r]){
		recover(bel[l]);
		for (int i=l;i<=r;++i)
			q[lis[i]]+=p[lis[i]]*c;
		h2[nh2++]=ans[bel[l]];
		push_heap(h2,h2+nh2);
		build(bel[l]);
		h1[nh1++]=ans[bel[l]];
		push_heap(h1,h1+nh1);
		return;
	}
	recover(bel[r]);
	for (int i=L[bel[r]];i<=r;++i)
		q[lis[i]]+=p[lis[i]]*c;
	h2[nh2++]=ans[bel[r]];
	push_heap(h2,h2+nh2);
	build(bel[r]);
	h1[nh1++]=ans[bel[r]];
	push_heap(h1,h1+nh1);
	
	for (int i=bel[l];i<=bel[r]-1;++i){
		h2[nh2++]=ans[i];
		push_heap(h2,h2+nh2);
		tag[i]+=c;
		getans(i);
		h1[nh1++]=ans[i];
		push_heap(h1,h1+nh1);
	}
}
inline void change(int x,int c){
	for (;x;x=fa[top[x]])
		add(dfn[top[x]],dfn[x],c);
}
int main(){
	freopen("tree.in","r",stdin);
	freopen("tree.out","w",stdout);
	n=input(),m=input();
	for (int i=1;i<=n;++i)
		a[i]=input();
	for (int i=1;i<n;++i)
		ed[i]={input(),input(),input()};
	sort(ed+1,ed+n,cmped);
	for (int i=1;i<=n;++i)
		ufs[i]=i;
	cnt=n;
	for (int i=1;i<n;++i){
		int xx=get(ed[i].x),yy=get(ed[i].y);
		++cnt;
		ufs[xx]=ufs[yy]=ufs[cnt]=cnt;
		fa[xx]=fa[yy]=cnt;
		mn[cnt]=ed[i].v;
	}
	for (int i=1;i<cnt;++i){
		e[ne]={i,last[fa[i]]};
		last[fa[i]]=e+ne++;
	}
	for (int i=1;i<=n;++i)
		sum[i]=a[i];
	dfs1(cnt),dfs2(cnt,cnt);
	for (int i=1;i<=cnt;++i)
		p[i]=mn[i],q[i]=sum[i]*mn[i];
	for (int i=1;i<=cnt;++i)
		if (top[i]==i){
			int B=sqrt(len[i]);
			for (int j=0;j*B<len[i];++j){
				nb++;
				for (int k=0;k<B && j*B+k<len[i];++k)
					bel[dfn[i]+j*B+k]=nb;
				L[nb]=dfn[i]+j*B;
				R[nb]=dfn[i]+min(j*B+B-1,len[i]-1);
				h[nb]=&st[L[nb]];
				build(nb);
				h1[nh1++]=ans[nb];
				push_heap(h1,h1+nh1);
			}
		}
	while (m--){
		int x=input(),v=input(),c=v-a[x];
		a[x]=v;
		change(x,c);
		while (h1[0]==h2[0]){
			pop_heap(h1,h1+nh1--);
			pop_heap(h2,h2+nh2--);
		}
		printf("%lld\n",h1[0]);
	}	
	return 0;
}

总结

分块大法好啊……

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值