[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; }
小结:比较有趣的一道题。
| 欢迎来原网站坐坐! >原文链接<