洛谷 [P3806] 点分治1 (点分治模板)

Link

https://www.luogu.org/problemnew/show/P3806

Description

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

Solution

 这是一道点分治的模板题。

 点分治一般有以下几种用法:

  • 求路径长度等于或小于等于 k k k的点对(路径条数);
  • 路径长度为 k k k的倍数;
  • 路径长度为 k k k且路径的边数最少;
  • 路径长度对某个数 x x x取余后等于 k k k;
  • 路径上经过的点的个数不能超过 k k k,且路径长度最大。

 对于树上的路径来说,可以分为两种:

  1. 经过根 r o o t root root;
  2. 包含于 r o o t root root的某一棵子树中,即不经过 r o o t root root

 那么对于第2种可以根据分治思想,将每一棵子树作为子问题,递归处理。对于第1种情况,我们可以从根节点 r o o t root root,分成 x − r o o t x-root xroot以及 r o o t − y root-y rooty两部分,我们用数组 d d d表述子节点到根节点 r o o t root root的距离,我们需要求的则是 d [ x ] + d [ y ] = = k d[x]+d[y]==k d[x]+d[y]==k,且 x x x y y y不在同一棵树内,映射到整棵树,则是对于任意一棵子树来说,找出所有满足上述条件的点对,这即是它的思想。

 那么它是怎么实现的呢,首先考虑到平衡的问题(即子树退化为链的情况),每次递归计算子树的时候都需要计算一次树的重心,以重心作为子树的根,然后计算。

 对于这棵树的某个子树来说(下文说的树都是它这个子树以及它的子树),首先计算该树内所有点的 d d d值。对于 x x x y y y,直接找它的不同子树的 x x x y y y是很复杂的,(枚举,暴力?)。我们可以这样做,计算出这个树里所有满足 d [ x ] + d [ y ] = = k d[x]+d[y]==k d[x]+d[y]==k的点对数 c n t 1 cnt1 cnt1,这个结果包括 x x x y y y在或者不在同一棵子树的所有情况,我们然后计算出它的所有子树里满足 d [ x ] + d [ y ] = = k d[x]+d[y]==k d[x]+d[y]==k的点对数 c n t 2 cnt2 cnt2,然后 c n t 1 − c n t 2 cnt1-cnt2 cnt1cnt2就是我们需要的结果,在这个过程种可以用递归实现。

 以上仅代表个人浅显见解,仅供个人理解。这里放上一篇写的很好的博客。
https://blog.csdn.net/qq_39553725/article/details/77542223

About

 在记录满足 d [ x ] + d [ y ] = = k d[x]+d[y]==k d[x]+d[y]==k的点对数时,考虑到树的边取值达到 1 e 7 1e7 1e7,于是想用 m a p map map,以 m a p [ d [ x ] + d [ y ] ] map[d[x]+d[y]] map[d[x]+d[y]]的方式记录,然而TLE了,于是改用 u n o r d e d e d m a p unordeded_map unordededmap,还是T,最后该用 1 e 8 1e8 1e8的数组才过。

 依次是 m a p map map u n o r d e r e d _ m a p unordered\_map unordered_map、数组实现的。

Code
#include<iostream>
#include<queue>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<map>
#include<unordered_map>
using namespace std;
#define maxn 10090
#define inf 0x3f3f3f3f
typedef long long ll;
struct edge{
    int u,v,next,w; 
}e[2*maxn];
int head[maxn],cnt;
void add(int x,int y,int w){
    e[cnt].u=x;
    e[cnt].v=y;
    e[cnt].w=w;
    e[cnt].next=head[x];
    head[x]=cnt++;
    e[cnt].u=y;
    e[cnt].v=x;
    e[cnt].w=w;
    e[cnt].next=head[y];
    head[y]=cnt++;
}
int read(){
    int x=0,f=1;
    char c=getchar();
    while(c<'0'||c>'9'){
        if(c=='-')f=-1;
        c=getchar();
    }
    while(c>='0'&&c<='9'){
        x=x*10+c-'0';
        c=getchar();
    }
    return x*f;
}
int S,cot,ans,res,n,m,k,root,sz[maxn],vis[maxn];
int  dis[maxn];
int mp[10000001];
//unordered_map<int,int>mp;
inline void getroot(int x,int fa){
    int maxsz=-1;
    sz[x]=1;
    for(int i=head[x];i!=-1;i=e[i].next){
        int v=e[i].v;
        if(vis[v]||v==fa)continue;
        getroot(v,x);
        sz[x]+=sz[v];
        maxsz=max(sz[v],maxsz);
    }
    maxsz=max(maxsz,S-sz[x]);
    if(res>maxsz)res=maxsz,root=x;
}
inline void getdis(int x,int fa,int len){
    dis[++cot]=len;
    for(int i=head[x];i!=-1;i=e[i].next){
        int v=e[i].v,w=e[i].w;
        if(vis[v]||v==fa)continue;
        getdis(v,x,len+w);
    }
}
inline void cal(int x,int len,int t){
    cot=0;
    getdis(x,0,len);
    for(int i=1;i<=cot;i++)
        for(int j=1;j<i;j++)
            mp[dis[i]+dis[j]]+=t;
}
inline void divide(int x){
    cal(x,0,1);
    vis[x]=1;
    for(int i=head[x];i!=-1;i=e[i].next){
        int v=e[i].v,w=e[i].w;
        if(vis[v])continue;
        cal(v,w,-1);
        S=sz[v],cot=0,res=inf;
        getroot(v,x);
        divide(root);
    }
}
inline void solve(){
    S=n,res=inf;
    getroot(1,0);
    divide(root);
}
int main(){
    n=read(),m=read();
    memset(head,-1,sizeof(head));
    int a,b,c;
    for(int i=0;i<n-1;i++){
        a=read(),b=read(),c=read();
        add(a,b,c);
    }
    solve();
    for(int i=0;i<m;i++){
        k=read();
        if(mp[k])printf("AYE\n");
        else printf("NAY\n");
    }
    return 0;
}

我们坚持一件事情,并不是因为这样做了会有效果,而是坚信,这样做是对的。
——哈维尔

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值