Luogu P3806 【模板】点分治1

题目大意

%   给定一棵有 n n n 个点,边权的树,回答 m m m 个询问,每次询问树上距离为 k k k 的点对是否存在。
  数据范围  n ⩽ 1 0 4 , m ⩽ 100 , 边权 ⩽ 10000 , k ⩽ 1 0 7 n\leqslant 10^4,m\leqslant 100,\texttt{\small边权}\leqslant 10000,k\leqslant 10^7 n104,m100,边权10000,k107

题解

%   点分治模板。
  考虑以 u u u 为根的子树,这棵子树上的路径有两种,一种是经过节点 u u u 的,另一种是不经过根节点的,考虑分治。对于大小为 1 1 1 的子树,无树上路径,直接返回。对于节点 u u u,合并不同的两个子树的路径,组成经过节点 u u u 的一条新路径。
  对于层数为 k k k 的树,时间复杂度为 Θ ( k log ⁡ 2 n ) \Theta(k\log_2n) Θ(klog2n),在平均情况下良好,但当树为一条链时,程序的时间复杂度将为 Θ ( n 2 ) \Theta(n^2) Θ(n2)
  对于一棵树 n n n 个节点的无根树,找到一个点,使得把树变成以该点为根的有根树时,最大子树的结点数最小,那这个点就是树的重心
  求解重心可以用DFS求出所有子树的大小,然后动态规划求解,时间复杂度为 Θ ( n ) \Theta(n) Θ(n)
  如果我们选取重心作为根,然后统计从根节点出发的所有路径( Θ ( n ) \Theta(n) Θ(n)),合并从根节点出发的路径,得到所有经过根节点的路径,接着删除根节点,对剩下的每个子树分别找重心,重复以上操作,便可求出所有路径。换句话说:每个点都是根节点,但所管理的子树大小不同,而且重心的性质保证了最坏情况下划分的子树总数和 Θ ( log ⁡ 2 n ) \Theta(\log_2 n) Θ(log2n) 同阶。
  在合并路径时,先将所有路径按长度排序先枚举询问( Θ ( m ) \Theta(m) Θ(m)),令当前询问长度为 k k k,再枚举每条路径( Θ ( n ) \Theta(n) Θ(n)),令路径长度为 w w w,最后在路径中二分查找长度为 k − w k-w kw 的路径( Θ ( log ⁡ 2 n ) \Theta(\log_2n) Θ(log2n)),注意此时两条路径不能属于同一个子树,因而合并路径的时间复杂度为 Θ ( m n log ⁡ 2 n ) \Theta(mn\log_2n) Θ(mnlog2n)
  因而程序的总时间复杂度为 T ( n ) = Θ ( m n log ⁡ 2 2 n ) \text{T}(n)=\Theta(mn\log_2^2 n) T(n)=Θ(mnlog22n)  代码如下

#include<bits/stdc++.h>
#define N 10010
using namespace std;
int n,m,num,e,x,y,z,a[110],rt,size,siz[N],f[N],head[N];
bool ans[110],vis[N];
struct edge{int v,w,pre;}edges[N<<1];
struct path{
	int dis,w;
	bool operator<(const path &a)const{return dis<a.dis;}
} dis[N];

template<typename T>
void maxx(T& a,const T &b){a<b? a=b:0;}
void add(int x,int y,int z){
    edges[++e]=(edge){y,z,head[x]};
    head[x]=e;
}

void getroot(int x,int fa){//找重心 O(n)
    f[x]=0;
    siz[x]=1;
    for(int i=head[x];i;i=edges[i].pre){
        int p=edges[i].v;
        if(vis[p]||p==fa) continue;
        getroot(p,x);
        siz[x]+=siz[p];
        maxx(f[x],siz[p]);
    }
    maxx(f[x],size-siz[x]);
    if(f[x]<f[rt]) rt=x;
}

void dfs(int x,int fa,int wh,int d){//O(n)
	dis[++num]=(path){d,wh};
	for(int i=head[x];i;i=edges[i].pre){
		int &p=edges[i].v;
		if(vis[p]||p==fa) continue;
		dfs(p,x,wh,d+edges[i].w);
	}
}

void work(int x){//O(nlogn) 
	num=0;
	for(int i=head[x];i;i=edges[i].pre){//O(n) 
		int p=edges[i].v;
		if(vis[p]) continue;
		dfs(p,x,p,edges[i].w);
	}
	dis[++num]=(path){0,0};
	sort(dis+1,dis+num+1);//O(nlogn) 
	for(int i=1;i<=m;++i){//O(nlogn) 
		if(ans[i]) continue;
		int l=1;//meet 
		while(l<num&&dis[l].dis+dis[num].dis<a[i]) l++;
		while(l<num&&!ans[i]){
			if(a[i]-dis[l].dis<dis[l].dis) break;
			int pot=lower_bound(dis+1,dis+1+num,(path){a[i]-dis[l].dis,0})-dis;
			while(l<=num&&dis[pot].dis+dis[l].dis==a[i]&&dis[pot].w==dis[l].w) pot++;
			if(dis[pot].dis+dis[l].dis==a[i]) ans[i]=1;
			l++;
		}
	}
}

void solve(int x){//O(nlog^2n)
	vis[x]=1;
	work(x);
	for(int i=head[x];i;i=edges[i].pre){
		int p=edges[i].v;
		if(vis[p]) continue;
		rt=0;
		siz[rt]=size=siz[p];
		getroot(p,0);
		solve(rt);//调用logn次 
	}
}

int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<n;++i){
        scanf("%d%d%d",&x,&y,&z);
        add(x,y,z);add(y,x,z);
    } for(int i=1;i<=m;++i)
		scanf("%d",a+i);
    f[rt]=size=n;
    getroot(1,0);
    solve(rt);
    for(int i=1;i<=m;++i)
        puts(ans[i]?"AYE":"NAY");
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值