• 【模板】点分治


    好的,我们先来看题:

    题目描述

    给定一棵有n个点的树

    询问树上距离为k的点对是否存在。

    输入输出格式

    输入格式:

    n,m 接下来n-1条边a,b,c描述a到b有一条长度为c的路径

    接下来m行每行询问一个K

    输出格式:

    对于每个K每行输出一个答案,存在输出“AYE”,否则输出”NAY”(不包含引号)

    输入输出样例

    输入样例#1: 复制
    2 1
    1 2 2
    2
    输出样例#1: 复制
    AYE

    说明

    对于30%的数据n<=100

    对于60%的数据n<=1000,m<=50

    对于100%的数据n<=10000,m<=100,c<=1000,K<=10000000

    1.什么是点分治?

    首先,分治大家应该很了解了吧,那么点分治其实与分治同理,就是将复杂的问题分解为很多很多细小的子问题,从而减少时间复杂度,就拿这道题来说,利用点分治就明显比爆搜快很多。。。

    2.点分治的原理:

    先来看一张图片(不错,是我盗的图(滑稽。。。))

    显然我们如果要求两点之间的距离,只有两种情况:经过根节点或者不经过根节点。并且第二种情况可以通过换根来转化为第一种情况,那么我们就可以愉快的进行分治了qaq

    3.如何实现?

    上面提到了一些原理,但是如果是这样的一张图呢?

    如果还是找根的的话你基本就凉凉了。。。

    因此,我们期望将分成的两个树越平均越好,重心就诞生啦!!!

    树重心的定义:找到一个点,其所有的子树中最大的子鼠节点数最少,那么这个点就是这棵鼠的重心,删去重心后,生成的多棵鼠尽可能平衡(不知道知否正确)

    4.重点来了!!!

    铺垫基本做完了,现在来讲这道题:

    其实也是比较简单的,由于没有强制在线,所以可以离线操作(废话)。

    我们从重心开始枚举子树的根节点,依次递归下去,每一次遇到满足题意的就++就可以啦!

    最后,附上本题代码:(代码中有详解qaq)

      1 #include<cstdio>
      2 #include<algorithm>
      3 using namespace std;
      4 const int inf=10000000;
      5 const int maxn=100010;
      6 struct EDGE//链前存边
      7 {
      8     int to,val,nxt;
      9 } edge[maxn<<1];
     10 int head[maxn]/*链前附带物*/,cnt/*边数*/,maxp[maxn]/*子树的最大大小*/,size[maxn]/*子树大小*/,dis[maxn]/*x点到重心的距离*/;
     11 int vis[maxn]/*访问标记*/,test[105]/*记录是否可行*/,ju[inf]/*判断是否存在这个距离*/,q[maxn]/*用于清除ju数组*/,rem[maxn]/*记录距离*/;
     12 int query[1010]/*记录询问*/;
     13 int sum/*(子)树的大小和*/,root/*重心*/,n,m;
     14 void add(int x,int y,int z)//链前加边
     15 {
     16     edge[++cnt].val=z;
     17     edge[cnt].to=y;
     18     edge[cnt].nxt=head[x];
     19     head[x]=cnt;
     20 }
     21 void getroot(int id,int fa)//找重心 
     22 {
     23     size[id]=1;
     24     maxp[id]=0;
     25     for(int i=head[id]; i; i=edge[i].nxt)//链前遍历 
     26     {
     27         if(edge[i].to==fa||vis[edge[i].to]!=0)
     28         {
     29             continue;
     30         }
     31         getroot(edge[i].to,id); 
     32         size[id]+=size[edge[i].to];//回溯时的大小加和 
     33         maxp[id]=max(size[edge[i].to],maxp[id]);//记录子树大小的最大值 
     34     }
     35     maxp[id]=max(maxp[id],sum-size[id]);//判断两个子树大小,记录最大值 
     36     if(maxp[id]<maxp[root])//判断所选重心是否合法 
     37     {
     38         root=id; 
     39     }
     40 }
     41 void getdis(int id,int fa)//求距离,没啥好说的吧。。。 
     42 {
     43     rem[++rem[0]]=dis[id];
     44     for(int i=head[id]; i; i=edge[i].nxt)
     45     {
     46         if(edge[i].to==fa||vis[edge[i].to]!=0)
     47         {
     48             continue;
     49         }
     50         dis[edge[i].to]=dis[id]+edge[i].val;
     51         getdis(edge[i].to,id);
     52     }
     53 }
     54 void clac(int id)
     55 {
     56     int now=0;
     57     for(int i=head[id]; i; i=edge[i].nxt)
     58     {
     59         if(vis[edge[i].to]!=0)
     60         {
     61             continue;
     62         }
     63         rem[0]=0;//利用rem【0】来实现一个变量的作用 
     64         dis[edge[i].to]=edge[i].val;//加上邻边的距离 
     65         getdis(edge[i].to,id);//继续向下找 
     66         for(int j=rem[0]; j; j--)
     67         {
     68             for(int k=1; k<=m; k++)//枚举每一个询问 
     69             {
     70                 if(query[k]>=rem[j])//如果大于或等于则找另一半 
     71                 {
     72                     test[k]|=ju[query[k]-rem[j]];//右面一项为1就返回1,为0就返回0 
     73                 }
     74             }
     75         }
     76         for(int j=rem[0]; j; j--)//存入可行解 
     77         {
     78             q[++now]=rem[j];
     79             ju[rem[j]]=1;
     80         }
     81     }
     82     for(int i=1; i<=now; i++)//清零 
     83     {
     84         ju[q[i]]=0;
     85     }
     86 }
     87 void slove(int id)
     88 {
     89     vis[id]=ju[0]=1;//标记为找过 
     90     clac(id);//以id为根进行扩散 
     91     for(int i=head[id]; i; i=edge[i].nxt)//逐个遍历 
     92     {
     93         if(vis[edge[i].to]!=0)
     94         {
     95             continue;
     96         }
     97         sum=size[edge[i].to];//更新sum值为目前to点的子树大小总和 
     98         root=0;
     99         maxp[root]=inf;
    100         getroot(edge[i].to,0);
    101         slove(root);//逐个判断 
    102     }
    103 }
    104 int main()
    105 {
    106     scanf("%d%d",&n,&m);
    107     for(int i=1; i<=n-1; i++)//加边 
    108     {
    109         int x,y,z;
    110         scanf("%d%d%d",&x,&y,&z);
    111         add(x,y,z);
    112         add(y,x,z);
    113     }
    114     for(int i=1; i<=m; i++)//储存询问的要求
    115     {
    116         scanf("%d",&query[i]);
    117     }
    118     maxp[root]=sum=n;//初始将其设为最大,从而确定重心
    119     getroot(1,0);//找重心
    120     slove(root);//开干
    121     for(int i=1; i<=m; i++)//华丽的输出结果
    122     {
    123         if(test[i]!=0)
    124         {
    125             printf("AYE
    ");
    126         }
    127         else
    128         {
    129             printf("NAY
    ");
    130         }
    131     }
    132     return 0;
    133 }
  • 相关阅读:
    .NET 2.0泛型集合类与.NET 1.1集合类的区别(二)
    关于插件的好文章
    MemberInfo.GetCustomAttributes和MemberDescriptor.Attributes获取特性的不同
    新一代编程语言
    .NET 2.0泛型集合类与.NET 1.1集合类的区别(一)
    发现一篇关于.NET 2.0中关于事务命名空间的好文章
    C# WinForm控件美化扩展系列之给TextBox加水印
    log4.net
    C# GDI+ 双缓冲
    C# WinForm控件美化扩展系列之ListBox
  • 原文地址:https://www.cnblogs.com/yufenglin/p/10397789.html
Copyright © 2020-2023  润新知