bzoj3052&uoj58 糖果公园 带修树上莫队

       bzoj上面的莫队除了小Z的袜子都是权限题啊/(ㄒoㄒ)/~~(欺负我们穷孩子),不过好在uoj上还有这么一道最经典的带修树上莫队,那就可以顺便把两个一起学辣~\(≧▽≦)/~

       首先讲一下树上莫队,寒假的时候lbn在学校里面讲过,好像和vfk的不太一样??感觉还是lbn的简单一点

       考虑一个括号序列,向下一层为'(',向上回溯为')',那么考虑任意两点之间的路径就是走若干个')'和若干个'('了,那么把'('和')'都看成是一个修改操作,就直接把树上的问题转移到序列上辣!!就可以用普通的莫队了。

       不过有几个问题需要注意。令f[i]表示括号序列中i这个点对顶的'('的位置,g[i]为')'的位置。

       1.如何求两个点(x,y)的路径在括号序列中对应的区间呢?一般是x向上走再向下走,也就是先向上回溯,再向下层走,因此是先走')'最后走'(',那么(x,y)对应的括号序列区间即(g[x],f[y]);但是有一种特殊情况,就是x是y的祖先,那么就是先走'('最后也走'(',那么(x,y)对应(f[x],f[y]);

       2.注意到在(x,y)的lca既不是x也不是y的时候,(x,y)对应的括号序列区间是不包含它们的lca的,需要特殊处理。

       然后树上莫队就变成普通的莫队了。分块和修改直接对括号序列操作即可。很简单吧!!(好像另外一种树上莫队的分块是对树进行类似于bzoj1086王室联邦题面中的那一种分块)


       下面讲一下带修莫队,假设是一个序列的带修莫队(否则用上述方法转化)。假设把序列分成m块,然后以l所在块为第一关键字,以r所在块为第二关键字,以时间t为第三关键字排序,那么现在考虑转移的情况:

       1.同一组(l所在块相同且r所在块也相同的操作为一组)中t的转移,由于同一块中t是升序排列的,因此每一组中t的转移是O(N)的。组数为m^2,因此总时间O(Nm^2);

       2.同一组中l和r的转移。在同一组中l和r的范围都不超过N/m,因此每一次转移都是O(N/m)的。总共有N次转移,因此是O(N^2/m);

       3.一组转移到另一组。每次最多O(N),而组数只有m^2,因此最多转移O(m^2)次,总时间复杂度O(Nm^2)。

       把三个合起来,可以看到带修莫队的时间复杂度为O(N(N/m+m^2)),当m=N^(1/3)时,有最小的时间复杂度O(N^(5/3)),这也是带修莫队的时间复杂度。


       那么在这道题目中,将上述两个合起来,就变成了我们想要的树上带修莫队辣!!

AC代码如下:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#define ll long long
#define N 100005
using namespace std;

int n,m,cnt1,cnt2,tot,dfsclk,f[N],g[N],fst[N],pnt[N<<1],nxt[N<<1],id[N<<1],blg[N<<1];
int bin[25],pos[N],fa[N][17],c[N],d[N],u[N],v[N],w[N],last[N];
struct node{ int l,r,t,id; }a[N],b[N]; ll ans[N],sum; bool vis[N];
int read(){
	int x=0; char ch=getchar();
	while (ch<'0' || ch>'9') ch=getchar();
	while (ch>='0' && ch<='9'){ x=x*10+ch-'0'; ch=getchar(); }
	return x;
}
void add(int x,int y){
	pnt[++tot]=y; nxt[tot]=fst[x]; fst[x]=tot;
}
void dfs(int x){
	f[x]=++dfsclk; id[dfsclk]=x; int i,p;
	for (i=1; bin[i]<=d[x]; i++) fa[x][i]=fa[fa[x][i-1]][i-1];
	for (p=fst[x]; p; p=nxt[p]){
		int y=pnt[p];
		if (y!=fa[x][0]){
			fa[y][0]=x; d[y]=d[x]+1; dfs(y);
		}
	}
	g[x]=++dfsclk; id[dfsclk]=x;
}
int lca(int x,int y){
	if (d[x]<d[y]) swap(x,y); int tmp=d[x]-d[y],i;
	for (i=0; bin[i]<=tmp; i++)
		if (tmp&bin[i]) x=fa[x][i];
	for (i=16; i>=0; i--)
		if (fa[x][i]!=fa[y][i]){ x=fa[x][i]; y=fa[y][i]; }
	return (x==y)?x:fa[x][0];
}
bool cmp(node x,node y){
	return blg[x.l]<blg[y.l] || blg[x.l]==blg[y.l] && blg[x.r]<blg[y.r] || blg[x.l]==blg[y.l] && blg[x.r]==blg[y.r] && x.t<y.t;
}
void mdy(int x){
	if (vis[x]) sum-=(ll)v[c[x]]*w[u[c[x]]--]; else
		sum+=(ll)v[c[x]]*w[++u[c[x]]];
	vis[x]^=1;
}
void chg(int x,int y){
	if (vis[x]){ mdy(x); c[x]=y; mdy(x); }else c[x]=y;
}
int main(){
	n=read(); m=read(); int cas=read(),i,l,r,t;
	bin[0]=1; for (i=1; i<=17; i++) bin[i]=bin[i-1]<<1;
	for (i=1; i<=m; i++) v[i]=read();
	for (i=1; i<=n; i++) w[i]=read();
	for (i=1; i<n; i++){
		l=read(); r=read(); add(l,r); add(r,l);
	}
	for (i=1; i<=n; i++) c[i]=last[i]=read();
	int sz=(int)pow(n,2.0/3);
	dfs(1); for (i=1; i<=dfsclk; i++) blg[i]=(i-1)/sz;
	while (cas--){
		t=read(); l=read(); r=read();
		if (t){
			if (f[l]>f[r]) swap(l,r);
			a[++cnt1].r=f[r]; a[cnt1].id=cnt1; a[cnt1].t=cnt2;
			a[cnt1].l=(lca(l,r)==l)?f[l]:g[l];
		} else{ b[++cnt2].l=l; b[cnt2].t=last[l]; last[l]=b[cnt2].r=r; }
	}
	sort(a+1,a+cnt1,cmp); l=1; r=0; t=1;
	for (i=1; i<=cnt1; i++){
		for (; t<=a[i].t; t++) chg(b[t].l,b[t].r); for (; t>a[i].t; t--) chg(b[t].l,b[t].t);
		while (l>a[i].l) mdy(id[--l]); while (l<a[i].l) mdy(id[l++]);
		while (r>a[i].r) mdy(id[r--]); while (r<a[i].r) mdy(id[++r]);
		int x=id[l],y=id[r],tmp=lca(x,y);
		if (x!=tmp && y!=tmp){ mdy(tmp); ans[a[i].id]=sum; mdy(tmp); }
		else ans[a[i].id]=sum;
	}
	for (i=1; i<=cnt1; i++) printf("%lld\n",ans[i]);
	return 0;
}


by lych

2016.3.10

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值