[bzoj3910]火车_并查集_倍增LCA

火车 bzoj-3910

题目大意:给定一棵n个节点的树,你需要顺次经过m个互不相同的节点,如果一个节点在之前的路径上被经过过,它不必再被特意经过。问走过的路径长度。

注释:$1\le n\le 5\cdot 10^5$,$1\le m\le 4\cdot 10^5$。


想法

考场上切了/xyx

考虑暴力:顺次枚举所有必经点然后暴力爬,标记为经过过即可,总时间复杂度为O(nm)。

我们发现如果两个点之间的一些路径是vis那么他们没有必要被在此枚举一遍。

所以如果一个节点被到达过我们将它的并查集内的Father连向它树上的父亲,表示这个节点已经被删去了。

再次遍历的时候我们爬并查集就行了。

更新答案的话我们用倍增求出两点的LCA,用dep值计算路径长度累加答案即可。

显然每个点只会被删除一次,而且只会被遍历一次因为遍历之后就会被删掉。

故总时间复杂度为O(n+m)。

最后,附上丑陋的代码... ...

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define N 500010 
using namespace std;
typedef long long ll;
int to[N<<1],nxt[N<<1],head[N],tot;
int dep[N],f[25][N],F[N];
bool vis[N]; int go[N];
inline void add(int x,int y) {to[++tot]=y; nxt[tot]=head[x]; head[x]=tot;}
int find(int x) {return F[x]==x?x:F[x]=find(F[x]);}
void dfs(int pos,int fa)
{
	// puts("Fuck");
	dep[pos]=dep[fa]+1; f[0][pos]=fa; for(int i=1;i<=23;i++) f[i][pos]=f[i-1][f[i-1][pos]];
	for(int i=head[pos];i;i=nxt[i]) if(to[i]!=fa) dfs(to[i],pos);
}
int lca(int x,int y)
{
	if(dep[x]<dep[y]) swap(x,y);
	for(int i=23;~i;i--) if(dep[f[i][x]]>=dep[y]) x=f[i][x];
	if(x==y) return x;
	for(int i=23;~i;i--) if(f[i][x]!=f[i][y]) x=f[i][x],y=f[i][y];
	return f[0][x];
}
inline void update(int x)
{
	vis[x]=true; for(int i=head[x];i;i=nxt[i]) if(to[i]!=f[0][x]) F[to[i]]=x;
}
void Lca(int x,int y)
{
	// puts("Fuck");
	int z=lca(x,y);
	while(dep[x]>dep[z])
	{
		update(x); x=f[0][find(x)];
	}
	// printf("Fuck %d\n",y);
	while(dep[y]>dep[z])
	{
		// printf("Fuck %d\n",y);
		update(y); 
		// printf("Shit %d\n",z);
		y=f[0][find(y)];
		// printf("Shit %d\n",z);
	}
	update(z);
	// puts("Fuck");
}
int main()
{
	// freopen("airline.in","r",stdin);
	// freopen("airline.out","w",stdout);
	int x,y; ll ans=0;
	int n,m,a; scanf("%d%d%d",&n,&m,&a); int pre=a; for(int i=1;i<n;i++) {scanf("%d%d",&x,&y); add(x,y); add(y,x);}
	for(int i=1;i<=n;i++) F[i]=i;
	dfs(1,1);
	// printf("%d %d %d\n",lca(1,2),lca(2,3),lca(3,4));
	for(int i=1;i<=m;i++) scanf("%d",&go[i]); for(int i=1;i<=m;i++)
	{
		if(!vis[go[i]])
		{
			// printf("%d\n",go[i]);
			ans+=dep[pre]+dep[go[i]]-dep[lca(pre,go[i])]*2;
			// printf("%d %d %d %d %d %d\n",pre,go[i],lca(pre,go[i]),dep[pre],dep[go[i]],dep[lca(pre,go[i])]);
			Lca(pre,go[i]);
			pre=go[i];
		}
	}
	printf("%lld\n",ans);
	return 0;
}

小结:比较有趣的一道题。

posted @ 2018-11-01 19:52  JZYshuraK_彧  阅读(128)  评论(0编辑  收藏