zoukankan      html  css  js  c++  java
  • Speed

    传送门:Speed

    题目大意

    给一棵n个点的无根树,每条树边i给出li和ri表示速度在[li,ri]内才能通过这条边。
    现在有m个询问,每个询问给出一个速度x,求以x的速度(不能改变)能在树上通过的路径长度的最大值(起点和终点任意)。
    n,m<=70000;1<=li,ri<=n;

    来源&写因

    这题是在dalao们成外集训最后一场考试的T3,听说当时无人AC,确实挺难的,我自问也只会写n^2暴力。
    正解充分运用了线段树的性质,前些天新学的线段树优化建图也有异曲同工之妙,感觉领悟到了线段树的一些精髓,所以来写篇题解。

    题解

    m跟n同级,也就是说得求出速度为1~n时的答案,再O(1)回答询问。
    对于一个速度x,如果我们只在树上留下速度区间包含x的边,删去其它边,会得到一个森林,答案就是其中最长直径。那么我们肯定要考虑动态维护一个森林的最长直径,也就是说,维护其中每一个连通块的最长链,支持加边操作。并查集加树的直径的性质[1]可以轻松实现。
    问题是哪些区间包含x呢?
    设n=8,x=3
    发现包含x的区间即为包含[3]或[3,4]或[1,4]的所有区间。
    这启发我们用线段树做。
    首先对于线段树上的每一个节点node,存下所有包含了node所代表区间的边(直接用链表存)。
    然后求答案时遍历一遍线段树,每走到一个节点node,就施加node的链表里存的边的影响,而每离开一个节点node,就撤销node的链表里存的边的影响。这样子当遍历到线段树的一个叶子节点时,设l=r=x,我们恰好施加了所有包含x的边的影响,答案就有了。
    注意到我们需要撤销影响,可持久化并查集,其实用个栈存操作信息,回溯时怎么改的就怎么改回去就好了,可以发现进栈元素个数是nlogn的。但是这样做的话并查集就绝不能路径压缩了,于是复杂度就变得相当玄学,然而实测即使是在链的情况也跑得飞起==
    //upd:其实可以按秩合并,能保证一次log的复杂度。
    自然语言太无力了,还是看代码吧。

    #include<bits/stdc++.h>
    using namespace std;
    const int N=70005,M=N*20;
    inline int read(){
    	int x=0,w=0;char ch=0;
    	while(!isdigit(ch)) w|=ch=='-',ch=getchar();
    	while(isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
    	return w?-x:x; 
    }
    vector<int> to[N];
    namespace sp{
    	int dep[N],fa[N],sz[N],son[N],top[N];
    	void dfs1(int x,int pre){
    		dep[x]=dep[pre]+1;fa[x]=pre;sz[x]=1;
    		for(int i=0;i<to[x].size();++i){
    			int &y=to[x][i];
    			if(y==pre) continue;
    			dfs1(y,x);
    			sz[x]+=sz[y];
    			if(sz[y]>sz[son[x]]) son[x]=y;
    		} 
    	}	
    	void dfs2(int x,int pre){
    		top[x]=pre;
    		if(son[x]) dfs2(son[x],pre);
    		for(int i=0;i<to[x].size();++i){
    			int &y=to[x][i];
    			if(y==son[x]||y==fa[x]) continue;
    			dfs2(y,y);
    		}	
    	}
    	int lca(int x,int y){
    		while(top[x]^top[y]){
    			if(dep[top[x]]<dep[top[y]]) x^=y^=x^=y;
    			x=fa[top[x]];
    		}
    		return dep[x]<dep[y]?x:y;
    	} 
    	int dist(int x,int y){return dep[x]+dep[y]-(dep[lca(x,y)]<<1);}
    }
    int n,m,cur=0;
    namespace st{
    	int fa[N],l[N],r[N],f[N];
    	void init(){
    		for(int i=1;i<=n;++i) fa[i]=l[i]=r[i]=i,f[i]=0;
    	}
    	inline int find(int x){return x==fa[x]?x:find(fa[x]);}
    	struct opt{
    		int id,x,val;
    	}q[N<<2];
    	using sp::dist;
    	void merge(int x,int y,int &ans){
    		x=find(x),y=find(y);
    		if(x==y) return ;
    		int res=0,nl,nr,t=-1;
    		if(f[x]>res) nl=l[x],nr=r[x],res=f[x];
    		if(f[y]>res) nl=l[y],nr=r[y],res=f[y];
    		t=dist(l[x],l[y]);
    		if(t>res) nl=l[x],nr=l[y],res=t;
    		t=dist(r[x],r[y]);
    		if(t>res) nl=r[x],nr=r[y],res=t;
    		t=dist(l[x],r[y]);
    		if(t>res) nl=l[x],nr=r[y],res=t;
    		t=dist(r[x],l[y]);
    		if(t>res) nl=r[x],nr=l[y],res=t;
    		q[++cur]=(opt){0,y,0};
    		q[++cur]=(opt){1,x,l[x]};
    		q[++cur]=(opt){2,x,r[x]};
    		q[++cur]=(opt){3,x,f[x]};
    		fa[y]=x,l[x]=nl,r[x]=nr,f[x]=res;
    		ans=max(ans,res); 
    	}
    	void retrace(int tim){
    		while(cur>tim){
    			switch(q[cur].id){
    				case 0:fa[q[cur].x]=q[cur].x;break;
    				case 1:l[q[cur].x]=q[cur].val;break;
    				case 2:r[q[cur].x]=q[cur].val;break;
    				case 3:f[q[cur].x]=q[cur].val;break;
    			} 
    			--cur;
    		}
    	}
    }
    namespace sg{
    	int tot,tail[N<<2];
    	struct edge{
    		int u,v,last;
    	}e[M];
    	#define tl id<<1
    	#define tr id<<1|1
    	#define mid (l+r>>1)
    	#define lson tl,l,mid
    	#define rson tr,mid+1,r 
    	void update(int id,int l,int r,int ll,int rr,int u,int v){
    		if(ll<=l&&r<=rr){
    			e[++tot]=(edge){u,v,tail[id]};
    			tail[id]=tot;
    			return ;
    		}
    		if(rr<=mid) update(lson,ll,rr,u,v);
    		else if(ll>mid) update(rson,ll,rr,u,v);
    		else update(lson,ll,rr,u,v),update(rson,ll,rr,u,v);
    	}
    	int ans[N];
    	void solve(int id,int l,int r,int res){
    		int tim=cur;
    		for(int p=tail[id];p;p=e[p].last) st::merge(e[p].u,e[p].v,res);
    		if(l==r){
    			ans[l]=res;
    			st::retrace(tim);
    			return ;
    		}
    		solve(lson,res),solve(rson,res);
    		st::retrace(tim);
    	}
    }
    int main(){
    	n=read(),m=read();
    	for(int i=1,u,v,l,r;i<n;++i){
    		u=read(),v=read(),l=read(),r=read();
    		to[u].push_back(v),to[v].push_back(u);
    		sg::update(1,1,n,l,r,u,v);
    	}
    	sp::dfs1(1,0);sp::dfs2(1,1);	
    	st::init();
    	sg::solve(1,1,n,0);
    	while(m--) printf("%d
    ",sg::ans[read()]);
    	return 0;
    }
    

    1. 设树A和树B,树A直径的端点为la和ra,树B直径的端点为lb和rb。在A和B之间连一条边后,得到的新树的直径的端点一定是la,ra,lb,rb中的两个。 ↩︎

  • 相关阅读:
    农历
    成熟度模型-数据安全
    vscode升级go插件
    关于作者
    SpringBoot入门十二(整合之项目打包部署运行)
    SpringBoot入门十一(整合之RedisTemplate的使用)
    SpringBoot入门十(整合之Junit测试)
    SpringBoot入门九(整合之通用mapper)
    SpringBoot入门八(整合之mybatis)
    SpringBoot入门七(整合之事务和连接池)
  • 原文地址:https://www.cnblogs.com/gosick/p/11517140.html
Copyright © 2011-2022 走看看