IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 数据结构与算法 -> [CF1336F]Journey -> 正文阅读

[数据结构与算法][CF1336F]Journey

Journey

题解

又是一道阴间大码量题,话说这跟LCT完全无关的题为什么拉到LCT的版块里呀。

首先,我们发现这道题其实我们可以分两种情况讨论,一种是 l c a lca lca相同的链,一种是 l c a lca lca不同的链。
由于这两种情况各自对应的链覆盖不同处比较大,所以还是分开写比较好。

我们先关注 l c a lca lca不同的情况,显然,应该是长这个样子的。
懒得画了,直接放官解的图。
在这里插入图片描述
显然,存在一个 l c a lca lca较高的链与 l c a lca lca较低的链,假设它们分别为图中的 A , B A,B A,B
那么对于这个较低的链 B B B,由于 A , B A,B A,B呈现祖先关系,所以 A A A至少需要覆盖 B B B往下的一条长度为 K K K的链。
即需要覆盖到图中的点 C C C或者点 D D D
我们可以将它转化为 A A A有一个端点在 C C C或者 D D D的子树中,如果是子树内的话,显然就可以映射到其 d f s dfs dfs序在某个区间中。
d f s dfs dfs序这就好维护,我们可以考虑使用线段树维护。
我们在条链的 l c a lca lca上处理这条链,这条链上就相当于给从 l c a lca lca向两端走 K K K步到的两个子树区间内 + 1 +1 +1,每个点上维护的就是该点处在多少个区间。
那我们每次查询就只需要询问链的两个端点在多少个区间,显然,这两个端点是不会有重复的区间的,毕竟这些链都只在该 l c a lca lca的一个子树内,我们这里只处理 l c a lca lca不同的链。
至于每次上传,就直接线段树合并就行了。
这不是时间复杂度是 O ( ( n + m ) log ? ? n ) O\left((n+m)\log\,n\right) O((n+m)logn)的。

至于 l c a lca lca相同的部分,它大概有这两种情况。
在这里插入图片描述
在这里插入图片描述
事实上,这两种都是十分类似的,我们可以用同一个方式处理。许多题解都提及用不同的方式处理,事实上是完全没必要的。
我们发现,对于两条 l c a lca lca相同的链 ( A , B ) (A,B) (A,B) ( C , D ) (C,D) (C,D),我们可以尝试将其中两条链的端点任意组合,我们显然有 4 4 4种方案,假设我们现在组合了 A , C A,C A,C,那我们就去计算 l c a ( A , C ) ? > l c a ( B , D ) lca(A,C)->lca(B,D) lca(A,C)?>lca(B,D)的长度。
我们发现,无论怎么组合,我们都会存在且进存在两种方案,它们的路径长度为原长,另外两种方案它们的长度为 0 0 0,分别是原链与就一个 l c a ( A , B , C , D ) lca(A,B,C,D) lca(A,B,C,D)
那么我们可以考虑将l每条链分成 A ? > B A->B A?>B B ? > A B->A B?>A两条放进去,与其它的链匹配,最后再将答案除以二。
对于两条链的匹配,就是对于 ( X , Y ) (X,Y) (X,Y) ( Z , W ) (Z,W) (Z,W)求出 l c a ( X , Z ) ? > l c a ( Y , W ) lca(X,Z)->lca(Y,W) lca(X,Z)?>lca(Y,W)的长度,去看它合不合法。
显然,这也是比较好维护的,我们可以考虑树上启发式合并。
由于我们要求的是 l c a ( X , Z ) ? > l c a ( Y , W ) lca(X,Z)->lca(Y,W) lca(X,Z)?>lca(Y,W)的长度不小于 K K K,那么我们可以在 l c a ( X , Z ) lca(X,Z) lca(X,Z)处,观察 l c a ( Y , W ) lca(Y,W) lca(Y,W)是不是应该在距离 l c a ( X , Z ) lca(X,Z) lca(X,Z)距离 K K K的地方。
转化一下,询问我们的链 ( X , Y ) (X,Y) (X,Y)上,距离 l c a ( X , Z ) lca(X,Z) lca(X,Z)距离为 K K K的点的子树内是否包含 W W W
那么我们相当于在树上启发式合并 ( X , Y ) (X,Y) (X,Y)的时候,直接询问距离当前的 K K K的点内部有多少个我们合并目标集合中的端点,这不是也可以用线段树合并嘛。
与上面一个不同的是,上面的是区间加单点查询,这是单点加区间查询。
只不过要启发式合并,所以对于一个 l c a lca lca O ( n log ? 2 n ) O\left(n\log^2 n\right) O(nlog2n)的。

但如果我们每个点都要这样做一遍的话显然是不行的,但我们实际上要用的点并没有这么多,只有需要进行启发式合并的点是有用的。
那么我们可以对这个点上所有有用的操作建出一棵虚树,在虚树上进行处理。
由于虚树的大小是 O ( 操 作 个 数 ) O\left(操作个数\right) O()的,所以这样的话我们这部分的时间复杂度就降到了 O ( m log ? 2 m ) O\left(m\log^2 m\right) O(mlog2m)

总时间复杂度 O ( n log ? n + m log ? 2 m ) O\left(n\log n+m\log^2 m\right) O(nlogn+mlog2m)

源码

#include<bits/stdc++.h>
using namespace std;
#define MAXN 150005
#define lowbit(x) (x&-x)
#define reg register
#define pb push_back
#define mkpr make_pair
#define fir first
#define sec second
typedef long long LL;
typedef unsigned long long uLL; 
typedef long double Ld;
typedef pair<int,int> pii;
const int INF=0x3f3f3f3f;
const int mo=1e9+7;
const int mod=1e5+7;
const int inv2=499122177;
const double jzm=0.999;
const int zero=2000;
const int n1=150;
const int orG=3,ivG=332748118;
const double Pi=acos(-1.0);
const double eps=1e-8;
template<typename _T>
_T Fabs(_T x){return x<0?-x:x;}
template<typename _T>
void read(_T &x){
	_T f=1;x=0;char s=getchar();
	while(s>'9'||s<'0'){if(s=='-')f=-1;s=getchar();}
	while('0'<=s&&s<='9'){x=(x<<3)+(x<<1)+(s^48);s=getchar();}
	x*=f;
}
template<typename _T>
void print(_T x){if(x<0)putchar('-'),print(-x);if(x>9)print(x/10);putchar(x%10+'0');}
LL gcd(LL a,LL b){return !b?a:gcd(b,a%b);}
int add(int x,int y,int p){return x+y<p?x+y:x+y-p;}
void Add(int &x,int y,int p){x=add(x,y,p);}
int qkpow(int a,int s,int p){int t=1;while(s){if(s&1)t=1ll*t*a%p;a=1ll*a*a%p;s>>=1;}return t;}
int n,m,K,head[MAXN],tot,dep[MAXN],f[MAXN][20];
int dfn[MAXN],rd[MAXN],idx,root[MAXN];LL ans;
int sta[MAXN<<1],stak,pre[MAXN],st[MAXN<<1],stk,id[MAXN];
vector<int>vec[MAXN],tmp[MAXN],G[MAXN];
struct ming{
	int lson,rson,sum;
	ming(){lson=rson=sum=0;}
};
struct ask{int u,v,w,id;}s[MAXN];
struct edge{int to,nxt;}e[MAXN<<1];
void addEdge(int u,int v){e[++tot]=(edge){v,head[u]};head[u]=tot;}
class SegmentTree{
	private:
		ming tr[MAXN*50];int tot;
	public:
		void insert(int &rt,int l,int r,int al,int ar){
			if(l>r||l>ar||r<al||al>ar)return ;
			if(!rt)rt=++tot;int mid=l+r>>1;
			if(al<=l&&r<=ar){tr[rt].sum++;return ;}
			if(al<=mid)insert(tr[rt].lson,l,mid,al,ar);
			if(ar>mid)insert(tr[rt].rson,mid+1,r,al,ar);
		}
		int query(int rt,int l,int r,int ai){
			if(l>r||l>ai||r<ai||!rt)return 0;
			if(l==r)return tr[rt].sum;
			int mid=l+r>>1,res=tr[rt].sum;
			if(ai<=mid)res+=query(tr[rt].lson,l,mid,ai);
			if(ai>mid)res+=query(tr[rt].rson,mid+1,r,ai);
			return res;
		}
		void modify(int &rt,int l,int r,int ai){
			if(l>r||l>ai||r<ai)return ;
			if(!rt)rt=++tot;tr[rt].sum++;
			if(l==r)return ;int mid=l+r>>1;
			if(ai<=mid)modify(tr[rt].lson,l,mid,ai);
			if(ai>mid)modify(tr[rt].rson,mid+1,r,ai);
		}
		int askSum(int rt,int l,int r,int al,int ar){
			if(l>r||l>ar||r<al||al>ar||!rt)return 0;
			if(al<=l&&r<=ar)return tr[rt].sum;
			int mid=l+r>>1,res=0;
			if(al<=mid)res+=askSum(tr[rt].lson,l,mid,al,ar);
			if(ar>mid)res+=askSum(tr[rt].rson,mid+1,r,al,ar);
			return res;
		}
		int merge(int x,int y,int l,int r){
			if(!x||!y)return x+y;tr[x].sum+=tr[y].sum;
			if(l==r)return x;int mid=l+r>>1;
			tr[x].lson=merge(tr[x].lson,tr[y].lson,l,mid);
			tr[x].rson=merge(tr[x].rson,tr[y].rson,mid+1,r);
			return x;
		}
		void clear(){for(int i=1;i<=tot;i++)tr[i]=ming();tot=0;}
}T;
int lca(int a,int b){
	if(dep[a]>dep[b])swap(a,b);
	for(int i=18;i>=0;i--)if(dep[f[b][i]]>=dep[a])b=f[b][i];
	if(a==b)return a;
	for(int i=18;i>=0;i--)if(f[a][i]^f[b][i])a=f[a][i],b=f[b][i];
	return f[a][0];
}
int findFa(int x,int dp){for(int i=18;i>=0;i--)if((dp&(1<<i)))x=f[x][i];return x;}
void dosaka1(int u,int fa){
	dep[u]=dep[fa]+1;dfn[u]=++idx;f[u][0]=fa;
	for(int i=1;i<19;i++)f[u][i]=f[f[u][i-1]][i-1];
	for(int i=head[u];i;i=e[i].nxt)
		if(e[i].to^fa)dosaka1(e[i].to,u);
	rd[u]=idx;
}
void dosaka2(int u,int fa){
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].to;if(v==fa)continue;
		dosaka2(v,u);root[u]=T.merge(root[u],root[v],1,n);
	}
	int siz=vec[u].size();
	for(int i=0;i<siz;i++){
		int x=vec[u][i],su=s[x].u,sv=s[x].v;
		ans+=T.query(root[u],1,n,dfn[su]);
		ans+=T.query(root[u],1,n,dfn[sv]);
	}
	for(int i=0;i<siz;i++){
		int x=vec[u][i],su=s[x].u,sv=s[x].v,w=s[x].w;
		if(dep[su]-dep[w]>=K)
			su=findFa(su,dep[su]-dep[w]-K),
			T.insert(root[u],1,n,dfn[su],rd[su]);
		if(dep[sv]-dep[w]>=K)
			sv=findFa(sv,dep[sv]-dep[w]-K),
			T.insert(root[u],1,n,dfn[sv],rd[sv]);
	}
}
bool cmp(int x,int y){return dfn[x]<dfn[y];}
signed main(){
	read(n);read(m);read(K);
	for(int i=1;i<n;i++){
		int u,v;read(u);read(v);
		addEdge(u,v);addEdge(v,u);
	}
	dosaka1(1,0);
	for(int i=1;i<=n;i++)id[i]=i;
	for(int i=1;i<=m;i++)
		read(s[i].u),read(s[i].v),s[i].id=i,
		vec[s[i].w=lca(s[i].u,s[i].v)].pb(i);
	dosaka2(1,0);T.clear();
	for(int i=1;i<=n;i++)root[i]=0;
	for(int i=1;i<=n;i++){
		int siz=vec[i].size();LL summ=0;stak=0;
		for(int j=0;j<siz;j++){
			int u=s[vec[i][j]].u,v=s[vec[i][j]].v;
			sta[++stak]=u;sta[++stak]=v;
		}
		sort(sta+1,sta+stak+1,cmp);
		stak=unique(sta+1,sta+stak+1)-sta-1;int lsk=stak; 
		for(int j=1;j<lsk;j++)sta[++stak]=lca(sta[j],sta[j+1]);
		sort(sta+1,sta+stak+1,cmp);
		stak=unique(sta+1,sta+stak+1)-sta-1;
		for(int j=1;j<=stak;j++)pre[sta[j]]=j,id[j]=j;stk=0;
		for(int j=0;j<siz;j++){
			int u=s[vec[i][j]].u,v=s[vec[i][j]].v;
			if(dep[u]+dep[v]-2*dep[s[vec[i][j]].w]<K)continue;
			tmp[pre[u]].pb(v);tmp[pre[v]].pb(u); 
		}
		for(int j=1;j<=stak;j++){
			while(stk&&rd[st[stk]]<dfn[sta[j]])stk--;
			if(stk)G[pre[st[stk]]].pb(j);st[++stk]=sta[j];
		}
		int L=dfn[i],R=rd[i];
		for(int j=stak;j>0;j--){
			int u=sta[j],siz=tmp[id[j]].size();
			for(int k=0,y;k<siz;k++){
				int v=tmp[id[j]][k];
				if(dep[u]-dep[i]>=K)summ+=T.askSum(root[u],L,R,L,R);
				else y=findFa(v,dep[v]+dep[u]-2*dep[i]-K),
					summ+=T.askSum(root[u],L,R,dfn[y],rd[y]);
				T.modify(root[u],L,R,dfn[v]);
			}
			siz=G[j].size();
			for(int k=0;k<siz;k++){
				int v=sta[G[j][k]],siz1=tmp[id[j]].size(),siz2=tmp[id[pre[v]]].size();
				int iu=id[pre[u]],iv=id[pre[v]];
				if(siz1>siz2)for(int l=0;l<siz2;l++){
					int x=tmp[iv][l],y;tmp[iu].pb(x);
					if(dep[u]-dep[i]>=K)summ+=T.askSum(root[u],L,R,L,R);
					else y=findFa(x,dep[x]+dep[u]-2*dep[i]-K),
						summ+=T.askSum(root[u],L,R,dfn[y],rd[y]);
				}
				else for(int l=0;l<siz1;l++){
					int x=tmp[iu][l],y;tmp[iv].pb(x);
					if(dep[u]-dep[i]>=K)summ+=T.askSum(root[v],L,R,L,R);
					else y=findFa(x,dep[x]+dep[u]-2*dep[i]-K),
						summ+=T.askSum(root[v],L,R,dfn[y],rd[y]);
				}
				if(siz1<=siz2)id[pre[u]]=id[pre[v]];
				root[u]=T.merge(root[u],root[v],L,R);
			}
		}
		ans+=summ/2LL; 
		for(int k=1;k<=stak;k++)tmp[k].clear(),G[k].clear();
		T.clear();for(int k=1;k<=stak;k++)root[sta[k]]=0;
	}
	printf("%lld\n",ans);
	return 0;
}

谢谢!!!

  数据结构与算法 最新文章
【力扣106】 从中序与后续遍历序列构造二叉
leetcode 322 零钱兑换
哈希的应用:海量数据处理
动态规划|最短Hamilton路径
华为机试_HJ41 称砝码【中等】【menset】【
【C与数据结构】——寒假提高每日练习Day1
基础算法——堆排序
2023王道数据结构线性表--单链表课后习题部
LeetCode 之 反转链表的一部分
【题解】lintcode必刷50题<有效的括号序列
上一篇文章      下一篇文章      查看所有文章
加:2022-03-04 15:50:05  更:2022-03-04 15:53:15 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/26 16:43:43-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码