【树链剖分】【UR #4】UOJ53 追击圣诞老人

很久之前发最近才看到被驳回了。
虽然已经退役很久了不过还是补一下吧。

原题地址
题目直接贴了。(这个东西应该是可以直接用的啊。)

这里写图片描述
这里写图片描述
这里写图片描述

【题目分析】
一看这个数据范围,标准的数据结构题,而且基本上就是 O ( n l o g n ) O(nlogn) O(nlogn)的算法了。
这道题花了我2h+才搞定,可以说我是很弱了。
注意一个城市可以经过多次。

【解题思路】
###算法1
暴力枚举所有长度不超过k的路线,复杂度 O ( n k ) O(n^k) O(nk),可以得到10pt

###算法2
考虑路线权值的增加对答案的影响,令 W ( A ) W(A) W(A)表示一种路线A的权值。
我们发现,若u能走到v,则 W ( u ) &lt; W ( v ) W(u)&lt;W(v) W(u)<W(v)
所以我们可以用一个堆来维护这个路线的权值。一开始将所有长度为1的路线放进堆里,然后进行k次操作。每次取出堆顶序列 A A A,然后进行扩展,将 A A A连到的所有路线都塞回去堆里。
因为一个点最多连向n个点,所以该算法时间复杂度为 O ( n k ⋅ l o g n k ) O(nk·lognk) O(nklognk),可以得到20pt

###算法3
考虑优化算法2,我们发现,对于每个序列 A A A的扩展,实际上不需要塞n条路线,我们只需要塞最优的至多k条就行了(因为已经取出了一些最优的,所以会少一些)。但这样对时间复杂度影响不大,那么我们考虑如何每次只塞一条路线?
实际上,我们只需要将每个点的出边按权值大小排序,然后用左儿子右兄弟表示法构图即可。这样我们的堆里面只有(n+k)个点。
据说时间复杂度是 O ( n 2 + k l o n g ( n + k ) ) O(n^2+klong(n+k)) O(n2+klong(n+k)),如果强行排序的话。关于排序的较差的方法个人没有细想,直接想再用堆维护就好了。。。所以我们就有了:

###算法4
显然算法3中排序的时间复杂度决定了我们最后的时间复杂度。
怎么更快的地将每个点所连到的边权排序呢?
我们既然已经用了一个堆,不妨再用一个。

我们可以将每个点能走到的点按权值建一个小根堆, 优先队列中记录这些信息:(权值, 最后一个点, 指向一个堆的指针)。

弹出一个序列 ( W , x , H ) (W,x,H) (W,x,H)时, 只要将 ( W + w H . l . i d − w x , H . l . i d , H . l ) (W+w_{H.l.id}−w_x,H.l.id,H.l) (W+wH.l.idwx,H.l.id,H.l) ( W + w H . r . i d − w x , H . r . i d , H . r ) (W+w_{H.r.id}−w_x,H.r.id,H.r) (W+wH.r.idwx,H.r.id,H.r) ( W + w H e a p x . i d , H e a p x . i d , H e a p x ) (W+w_{Heap_x.id},Heap_x.id,Heap_x) (W+wHeapx.id,Heapx.id,Heapx)放入优先队列即可。

其中 H . i d H.id H.id表示堆 H H H中权值最小的点的编号, H e a p i Heap_i Heapi表示一个包含点i能走到的所有点的堆。

现在问题转化成,对每个 i i i求出 H e a p i Heap_i Heapi
我们用可并堆(如左偏树)预处理第 i i i个点到根的路径上前 2 j 2^j 2j个点的堆, 然后每个点能走到的点可以拆成三条链,于是可以用倍增得到。

时间复杂度 O ( n l o g 2 n + k l o g ( n + k ) ) O(nlog^2n+klog(n+k)) O(nlog2n+klog(n+k)),空间复杂度 O ( n l o g 2 n + k ) O(nlog^2n+k) O(nlog2n+k)。 能得到60pt。

###算法5(我并没有想到)
考虑算法3,考虑算法三, 如果我们能在 O ( f ( n ) ) O(f(n)) O(f(n))的时间内求得第 i i i个点能走到的点中,权值第 j j j大的点, 就能在 O ( g ( n ) + k l o g ( n + k ) + k ⋅ f ( n ) ) O(g(n)+klog(n+k)+k⋅f(n)) O(g(n)+klog(n+k)+kf(n))的时间内解决原问题,其中 O ( g ( n ) ) O(g(n)) O(g(n))的时间用来预处理。

算法三中 f ( n ) = O ( 1 ) f(n)=O(1) f(n)=O(1)), g ( n ) = O ( n 2 ) g(n)=O(n^2) g(n)=O(n2)

但在这里,我们用主席树(可持久化线段树前缀和)来维护 1 ∼ i 1∼i 1i路径上点的权值,就可以使 f ( n ) = O ( l o g n ) f(n)=O(logn) f(n)=O(logn), g ( n ) = O ( n l o g n ) g(n)=O(nlogn) g(n)=O(nlogn)

于是总的时间复杂度为 O ( n l o g n + k l o g ( n + k ) ) O(nlogn+klog(n+k)) O(nlogn+klog(n+k)),空间复杂度为 O ( n l o g n + k ) O(nlogn+k) O(nlogn+k)

能通过1 ∼ 16号测试点。 对于 n = 5 × 1 0 5 n=5×10^5 n=5×105的数据会MLE。

###算法6
考虑对算法4进行优化,对于一条链 ( u , v ) (u,v) (u,v),将这条链上的点建成一个堆 H H H, 那么 H . i d H.id H.id就是这条链上权值最小的点。 我们将这条链从这个点处断开, 那么剩下的两段分别对应于 H . l H.l H.l H . r H.r H.r

我们用一个二元组 ( a , b ) (a,b) (a,b)表示 ( a , b ) (a,b) (a,b)这条链的堆, 就不用预处理出算法四中的 H e a p i Heapi Heapi了。

复杂度为 O ( n l o g n + k l o g ( n + k ) + k ⋅ f ( n ) ) O(nlogn+klog(n+k)+k⋅f(n)) O(nlogn+klog(n+k)+kf(n)), 其中 f ( n ) f(n) f(n)为求出一条链上权值最小的点的时间复杂度。

用 倍增 or LCT or 树链剖分(要预处理每条重链的前缀minmin) 都可以做到 f ( n ) = O ( l o g n ) f(n)=O(logn) f(n)=O(logn)

那么时间复杂度就是 O ( n l o g n + k l o g ( n + k ) ) O(nlogn+klog(n+k)) O(nlogn+klog(n+k)),跟算法五一样。

如果你用倍增,空间复杂度是 O ( n l o g n + k ) O(nlogn+k) O(nlogn+k),很有可能会MLE。

如果你用LCT,空间复杂度是 O ( n + k ) O(n+k) O(n+k)的,但是常数太大,很有可能会TLE。

用树链剖分就可以通过所有测试点了。

【总结】
做这道题给了我一个很重要的启发,其实挺多OI题的部分分是在为AC作铺垫,对于一个点一个个瓶颈的解决,可能正是通向AC的道路。可以说是一道毒瘤好题吧。

另外得到了一个重点:inline对于函数的加速并不是无代价的,它会通过内存加速,对于这题来说,我的100pt程序由于使用了inline,开始只有80pt,而我MLE了很久才想到这个可能性,这样才最终通过了这题。

####UPDATE忘记贴代码
【代码】

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

const int INF=2e9;
const int MAXN=5e5+5;

int n,m,topp,cnt,ans,tot;
int head[MAXN],d[MAXN],fa[MAXN],f[MAXN];
int siz[MAXN],son[MAXN],top[MAXN],vi[MAXN<<3],wi[MAXN];
int b[MAXN][5],c[MAXN][5],p[MAXN],fp[MAXN],ll[MAXN];

struct Tway
{
	int nex,v;
};
Tway e[MAXN];

struct Tnode
{
	int s,t,k,w;
};
Tnode q[MAXN*5];

bool operator <(const Tnode &x,const Tnode &y)
{
	return x.w>y.w;
}

int calc(int x,int y)
{
	return wi[x]<wi[y]?x:y;
}

void dfs(int x,int ff)
{
	d[x]=d[ff]+1;siz[x]=1;fa[x]=ff;
	for(int i=head[x];i;i=e[i].nex)
	{
		int v=e[i].v;
		if(v==ff)
			continue;
		dfs(v,x);
		siz[x]+=siz[v];
		if(siz[v]>siz[son[x]])
			son[x]=v;
	}
}

 void dfss(int x,int ff)
{
	p[x]=++tot;fp[tot]=x;
	if(son[ff]==x)
		top[x]=top[ff];
	else
		top[x]=x;
	if(son[x])
	{
		f[son[x]]=calc(f[x],son[x]);
		dfss(son[x],x);
	}
	for(int i=head[x];i;i=e[i].nex)
	{
		int v=e[i].v;
		if(v==ff || v==son[x])
			continue;
		f[v]=v;
		dfss(v,x);
	}
}

int LCA(int x,int y)
{
	while(top[x]!=top[y])
	{
		if(d[top[x]]<d[top[y]])
			swap(x,y);
		x=fa[top[x]];
	}
	return d[x]<d[y]?x:y;
}

int lastt(int x,int y)
{
	int tmp;
	while(top[x]!=top[y])
	{
		tmp=top[y];
		y=fa[tmp];
	}
	return x==y?tmp:son[x];
}

void build(int rt,int l,int r)
{
	if(l==r)
	{
		vi[rt]=fp[l];
		return;
	}
	int mid=(l+r)>>1;
	build(rt<<1,l,mid);
	build(rt<<1|1,mid+1,r);
	vi[rt]=calc(vi[rt<<1],vi[rt<<1|1]);
}

void query(int rt,int l,int r,int L,int R)
{
	if(L<=l && r<=R)
	{
		ans=calc(ans,vi[rt]);
		return;
	}
	int mid=(l+r)>>1;
	if(L<=mid)
		query(rt<<1,l,mid,L,R);
	if(R>mid)
		query(rt<<1|1,mid+1,r,L,R);
/*	if(L==l && r==R)
	{
		ans=calc(ans,vi[rt]);
		return;
	}
	int mid=(l+r)>>1;
	if(R<=mid)
		query(rt<<1,l,mid,L,R);
	else
	if(L>mid)
		query(rt<<1|1,mid+1,r,L,R);
	else
		query(rt<<1,l,mid,L,mid),query(rt<<1|1,mid+1,r,mid+1,R);*/
}

int getp(int x,int y)
{
	ans=0;
	while(top[x]!=top[y])
	{
		ans=calc(ans,f[y]);
		y=fa[top[y]];	
	}
//	printf("%d %d %d %d..\n",x,y,p[x],p[y]);
	query(1,1,n,p[x],p[y]);
	return ans;
}

void add(int u,int v)
{
	++cnt;
	e[cnt].v=v;e[cnt].nex=head[u];
	head[u]=cnt;
}

void gdd(int x,int y,int z)
{
	b[x][++ll[x]]=y;
	c[x][ll[x]]=z;
}

void init()
{
	scanf("%d%d",&n,&m);
	wi[0]=INF;
	for(int i=1;i<=n;++i)
		scanf("%d",&wi[i]);
	for(int i=2;i<=n;++i)
	{
		int x;
		scanf("%d",&x);
		add(x,i);
	}
	
	dfs(1,0);dfss(1,0);
	build(1,1,n);
//for(int i=1;i<=n;++i)
//printf("%d %d %d\n",son[i],top[i],f[i]);	
	for(int i=1;i<=n;++i)
	{
		int x,y,z,xx,yy;
		scanf("%d%d%d",&x,&y,&z);
		xx=LCA(x,y);yy=LCA(y,z);
//		printf("%d %d\n",xx,yy);
		if(d[xx]>d[yy])
			swap(xx,yy),swap(x,z);
		else
		if(d[xx]==d[yy])
			swap(x,y),yy=LCA(y,z);
			
		q[++topp]=(Tnode){i,i,i,wi[i]};
		push_heap(q+1,q+topp+1);
		gdd(i,xx,x);
		if(xx!=z)
			gdd(i,lastt(xx,z),z);
		if(yy!=y)
			gdd(i,lastt(yy,y),y);
	}
	
/*	while(topp!=0)
	{
		Tnode tp=q[1];pop_heap(q+1,q+topp+1);topp--;
		printf("%d %d %d %d\n",tp.s,tp.t,tp.k,tp.w);
	}*/
}

void solve()
{
	while(m--)
	{
		int x,y;
		Tnode tp=q[1];pop_heap(q+1,q+topp+1);topp--;
		printf("%d\n",tp.w);
//		printf("%d %d %d %d\n",tp.s,tp.t,tp.k,tp.w);
		
		if(tp.s!=tp.k)
		{
			x=getp(tp.s,fa[tp.k]);
			q[++topp]=(Tnode){tp.s,fa[tp.k],x,tp.w-wi[tp.k]+wi[x]};
			push_heap(q+1,q+topp+1);
//			printf("@%d %d %d %d\n",tp.s,tp.t,tp.k,tp.w);
//			printf("x:%d\n",x);
		}
		if(tp.t!=tp.k)
		{
			x=lastt(tp.k,tp.t);
			y=getp(x,tp.t);
			q[++topp]=(Tnode){x,tp.t,y,tp.w-wi[tp.k]+wi[y]};
			push_heap(q+1,q+topp+1);
//			printf("@%d %d %d %d\n",tp.s,tp.t,tp.k,tp.w);
//			printf("x:%d y:%d\n",x,y);
		}
//		printf("ll:%d\n",ll[tp.k]);
		for(int i=1;i<=ll[tp.k];++i)
		{
			x=getp(b[tp.k][i],c[tp.k][i]);
			q[++topp]=(Tnode){b[tp.k][i],c[tp.k][i],x,tp.w+wi[x]};
			push_heap(q+1,q+topp+1);
//			printf("@%d %d %d %d\n",tp.s,tp.t,tp.k,tp.w);
//			printf("!%d %d %d\n",x,b[tp.k][i],c[tp.k][i]);
		}
	}
}

int main()
{
//	freopen("UOJ53.in","r",stdin);
//	freopen("UOJ53.out","w",stdout);
	
	init();
	solve();
	
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值