zoukankan      html  css  js  c++  java
  • 初学点分治

    前言

    在我的心目中,点分治是一个非常难的算法,但在解决一些树上问题时也非常实用。为此,我特地去学了学点分治这个高深的算法。

    什么是树的重心

    在学习点分治之前,我们先来解决一个问题:什么是树的重心

    在一棵树上找到一个点,使得删去这个点后得到的子树中节点数最大的子树最小,那么这个点就叫做树的重心

    那么树的重心具体有什么作用呢?

    这在之后会提到。

    下面,让我们先来看一看点分治的核心思想。

    核心思想

    以一道模板题为例:【洛谷3806】【模板】点分治1

    第一步,我们要找到一个节点\(rt\),将无根树转化为有根树

    不难发现,在这棵树上的路径只有两种:

    1. 经过根节点的路径。

    2. 不经过根节点的路径,则这条路径必定位于当前根节点的某个子树中。

    对于第一种情况,我们可以在确定根节点的情况下在\(O(n)\)的时间复杂度下处理掉。

    而对于第二种情况,我们就需要继续处理这条路径所在的子树,并重复以上步骤,求出答案

    这个时候就不难想到分治了。

    我们可以将当前处理到的子树分成若干个小子树,分别求出答案。

    这时我们再考虑,\(rt\)取什么节点才会使复杂度最优?

    我们可以取树的重心,使得剩下的子树大小尽量平均,时间复杂度也会大大优化,大致为\(O(nlog^2n)\)

    代码

    #include<bits/stdc++.h>
    #define max(x,y) ((x)>(y)?(x):(y))
    #define min(x,y) ((x)<(y)?(x):(y))
    #define LL long long
    #define swap(x,y) (x^=y,y^=x,x^=y)
    #define abs(x) ((x)<0?-(x):(x))
    #define Fsize 100000
    #define tc() (FinNow==FinEnd&&(FinEnd=(FinNow=Fin)+fread(Fin,1,Fsize,stdin),FinNow==FinEnd)?EOF:*FinNow++)
    #define N 10000
    #define M 100
    #define add(x,y,z) (e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y,e[ee].val=z)
    char Fin[Fsize],*FinNow=Fin,*FinEnd=Fin;
    using namespace std;
    const char ans[2][4]={"NAY","AYE"};
    int n,m,rt,top,ee=0,lnk[N+5],Size[N+5],MaxSize[N+5],used[N+5],dis[N+5],Stack[N+5],Exist[10000005];
    struct edge
    {
        int to,nxt,val;
    }e[2*N+5];
    struct Query
    {
        int Question,Answer;
    }q[M+5];
    inline void read(int &x)
    {
        x=0;static char ch;
        while(!isdigit(ch=tc()));
        while(x=(x<<3)+(x<<1)+ch-48,isdigit(ch=tc()));
    }
    inline void GetRt(int x,int lst,int sum)//确定根节点,即找到树的重心
    {
        register int i;
        for(Size[x]=1,MaxSize[x]=0,i=lnk[x];i;i=e[i].nxt)//初始化当前节点大小为1,最大子树大小为0
        {
            if(used[e[i].to]||!(e[i].to^lst)) continue;//如果当前边连向的节点为根节点,或是上一个访问到的节点,就跳过
            GetRt(e[i].to,x,sum),Size[x]+=Size[e[i].to],MaxSize[x]=max(MaxSize[x],Size[e[i].to]);//对当前边连向的节点进行操作,更新当前子树的大小以及最大子树大小
        }
        if((MaxSize[x]=max(MaxSize[x],sum-Size[x]))<MaxSize[rt]) rt=x;//如果当前节点最大子树节点数比原本找到的重心的最大子树节点数小,就更新重心
    }
    inline void dfs(int x,int lst)//搜索处理出答案
    {
        register int i;
        for(Stack[++top]=dis[x],i=lnk[x];i;i=e[i].nxt)//将到达当前节点的距离加入栈中,枚举与当前节点相邻的每一个节点
            if(e[i].to^lst&&!used[e[i].to]) dis[e[i].to]=dis[x]+e[i].val,dfs(e[i].to,x);//如果当前边连向的节点不是根节点且不是上一个访问到的节点,就继续dfs当前边连向的节点
    }
    inline void F5(int x,int t)//更新答案
    {
        register int i;
        for(i=1;i<=m;++i) if(q[i].Question>=x) q[i].Answer|=!(Exist[q[i].Question-x]^t);//如果存在一条为q[i].Question的路径,就将q[i].Answer更新为1
    }
    inline void GetAns(int x)//处理以x为根节点的子树
    {
        register int i,j;
        for(used[Exist[0]=x]=1,i=lnk[x];i;i=e[i].nxt)//标记当前节点已作为根节点,枚举每一个与当前节点相邻的节点
        {
            if(used[e[i].to]) continue;//如果枚举到的节点已作为根节点,就跳过
            dis[e[i].to]=e[i].val,dfs(e[i].to,0);//dfs一遍,求出根节点到这个子树中每一个节点的距离
        	for(j=1;j<=top;++j) F5(Stack[j],x);//对于每一个距离更新答案
        	while(top) Exist[Stack[top--]]=x;//将栈中的元素一个个标记为存在
        }
        for(i=lnk[x];i;i=e[i].nxt)//枚举每一个与当前节点相邻的节点
            if(!used[e[i].to]) GetRt(e[i].to,rt=0,Size[e[i].to]),GetAns(rt);//如果枚举到的节点没有被作为过根节点,就对这个子树进行处理
    }
    int main()
    {
        register int i,x,y,z;
        for(read(n),read(m),i=1;i<n;++i) read(x),read(y),read(z),add(x,y,z),add(y,x,z);
        for(i=1;i<=m;++i) read(q[i].Question);
        for(MaxSize[0]=1e9,GetRt(1,rt=0,n),GetAns(rt),i=1;i<=m;++i) puts(ans[q[i].Answer]);//点分治并输出答案
        return 0;
    }
    
    败得义无反顾,弱得一无是处
  • 相关阅读:
    【Gamma】Scrum Meeting 1
    【Gamma】设计与计划
    第五次作业 5.线性回归算法
    第四次作业 4.K均值算法--应用
    第三次作业 3.K均值算法
    第二次作业 2.机器学习相关数学基础
    第一次作业 机器学习概述
    第十五次 语法制导的语义翻译
    第十四次作业 算符优先分析
    第十三次作业 自下而上语法分析
  • 原文地址:https://www.cnblogs.com/chenxiaoran666/p/DotSolver.html
Copyright © 2011-2022 走看看