Luogu P3806 【模板】点分治1

题目大意

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

题解

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值