P3806 【模板】点分治1

题意:
给定一棵有 n 个点的树,询问树上距离为 k 的点对是否存在。

题解:
​ 因为题目是要求路径长为k的路径条数,所以solve函数返回的是过x节点的长度为k的路径。
而这路径长度是可以用 O ( n ) O(n) O(n) 的方法求出
点分治,没啥好说的。

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int maxn=2e5+10;

struct E{
    int to,w,next;
}edge[maxn];
int head[maxn],cnt;
int maxp[maxn],dis[maxn],sz[maxn];
bool visited[maxn],judge[10000000];
int sum,rt,q[maxn],nowcnt,temp[maxn];
int n,m;
void getrt(int x,int fa){  
    sz[x]=1,maxp[x]=0;//maxp初始化为最小值
    //遍历所有儿子,用maxp保留最大大小的儿子大小
    for(int i=head[x];~i;i=edge[i].next){
        int to=edge[i].to;
        //int w=edge[i].w;
        if(to==fa||visited[to]) continue;  //被删掉的也不算
        getrt(to,x);
        sz[x]+=sz[to];
        if(sz[to]>maxp[x]) maxp[x]=sz[to]; //更新maxp
    }
    maxp[x]=max(maxp[x],sum-sz[x]);
    if(maxp[x]<maxp[rt]) rt=x;
}

void add(int u,int v,int w){
    edge[cnt].to=v;
    edge[cnt].w=w;
    edge[cnt].next=head[u];
    head[u]=cnt++;
}

void getdis(int x,int fa){   
    temp[nowcnt++]=dis[x];
    for(int i=head[x];~i;i=edge[i].next){
        int v=edge[i].to;
        if(v==fa||visited[v]) continue;
        dis[v]=dis[x]+edge[i].w;
        getdis(v,x);
    }
}
int ans[105];
void sol(int x){
    queue<int> que;
    for(int i=head[x];~i;i=edge[i].next){
        int v=edge[i].to;
        if(visited[v]) continue;
        nowcnt=0;   //注意置零计数器
        dis[v]=edge[i].w;
        getdis(v,x);  //把距离都处理出来
        for(int j=0;j<nowcnt;j++){  //遍历所有距离
            for(int k=1;k<=m;k++){  //遍历所有询问
                if(q[k]>=temp[j]){  //如果询问大于单条路径的长度,就有可能存在
                    ans[k]|=judge[q[k]-temp[j]];
                }
            }
        }
        for(int j=0;j<nowcnt;j++){  //把存在的单挑路径长度标为true,供下文使用
            que.push(temp[j]);
            judge[temp[j]]=true;
        }
    }
    while(!que.empty()){
        judge[que.front()]=false;
        que.pop();
    }
}
void divide(int x){  
    visited[x]=judge[0]=true;  //删除根
    sol(x);  //计算经过根节点的路径
    for(int i=head[x];~i;i=edge[i].next){
        int v=edge[i].to;
        if(visited[v]) continue;
        maxp[rt=0]=sum=sz[v];  //重心设为0,把maxp[0]至为最大值
        getrt(v,0);
        getrt(rt,0);  //与主函数相同
        divide(rt);
    }
}
signed main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    memset(head,-1,sizeof head);
    memset(visited,false,sizeof visited);
    cin>>n>>m;
    sum=n;
    for(int i=1;i<n;i++){
        int u,v,w;
        cin>>u>>v>>w;
        add(u,v,w);
        add(v,u,w);
    }
    maxp[0]=sum=n;  //maxp[0]设为最大值
    getrt(1,0);  //找重心
    getrt(rt,0);  //此时siz数组存放的是1为根的时的大小,需要以找出的重心为根重算。
    for(int i=1;i<=m;i++) cin>>q[i];
    divide(rt);   //找好重心就可以分治了
    for(int i=1;i<=m;i++){
        if(ans[i]) cout<<"AYE"<<endl;
        else cout<<"NAY"<<endl;
    }
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值