luogu P3806 【模板】点分治1

题目描述

给定一棵有n个点的树

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

(多次询问&&可离线)

 

我们先随意指定一个虚拟根root,将这棵树转化成无根树

树上的路径可以分为两类,

1.经过根节点u的路径

2.完全在u子树里(不经过u)的

对于1,用dis表示当前结点到根节点root的路径长度, 则root的子树中两个节点u到v的路径长即为dis[u]+dis[v]

对于2,u到v的路径完全在root的某个子树内, 那么就找到这棵子树的根,对它再求一次第一种路径

就是不断的寻找重心,把原来的树分成很多小的树,并对每个子树分别求解

maxx[u]表示u的儿子的子树中,最大的大小

则树的重心就是maxx值最小的那个节点

 

 

  1 #include<cstdio>
  2 #include<iostream>
  3 #define maxn 100010
  4 using namespace std;
  5 int n,m,text[maxn],t[maxn],root; 
  6 int sum,size[maxn],maxx[maxn];
  7 int head[maxn*3],nxt[maxn*3],to[maxn*3],val[maxn*3],cnt;
  8 int vis[maxn],vis1[maxn],vis2[maxn],dis[maxn],judge[10000010];
  9 void add(int a,int b,int v)
 10 {
 11     cnt++;
 12     nxt[cnt]=head[a];
 13     head[a]=cnt;
 14     to[cnt]=b;
 15     val[cnt]=v;
 16 }
 17 void getroot(int u,int fa)//找到以u为跟的子树的重心 
 18 {
 19     size[u]=1;
 20     for(int i=head[u];i;i=nxt[i])
 21     {
 22         if(to[i]==fa||vis[to[i]]) continue;
 23         getroot(to[i],u);
 24         size[u]+=size[to[i]];//u的子树的大小包括所有子树的大小的和加一 
 25         maxx[u]=max(maxx[u],size[to[i]]);
 26     }
 27     maxx[u]=max(maxx[u],sum-maxx[u]);//保存u把整棵树分成两部分后大的部分的大小 
 28     if(maxx[u]<maxx[root]) root=u;//重心几乎把树分成平均两部分 
 29     return;
 30 }
 31 void getdis(int u,int f)
 32 {
 33     vis2[++vis2[0]]=dis[u];   //保存root到根节点中每一个点的距离 
 34     for(int i=head[u];i;i=nxt[i])
 35     {
 36         if(vis[to[i]]||to[i]==f) continue;
 37         dis[to[i]]=dis[u]+val[i]; //dis[u]保存此时的root到u的距离 
 38         getdis(to[i],u);
 39     }
 40     return;
 41 }
 42 void qiu(int u)
 43 {
 44     vis1[0]=0;
 45     for(int i=head[u];i;i=nxt[i])
 46     {
 47         if(vis[to[i]]) continue;
 48         vis2[0]=0;
 49         dis[to[i]]=val[i];
 50         getdis(to[i],u);
 51         for(int j=1;j<=vis2[0];j++)
 52             for(int o=1;o<=m;o++)
 53                 if(text[o]>=vis2[j]&&t[o]==0) t[o]=judge[text[o]-vis2[j]];
 54                 //若u的子树中存在两条以u为节点的不同路径经长度和为 text[o],那么就存在长度为text[u]的路径 
 55         for(int j=1;j<=vis2[0];j++)
 56         {
 57             vis1[++vis1[0]]=vis2[j];
 58             judge[vis2[j]]=1;
 59             //保存存在的从root到u的子树的点的路径长度 
 60         } 
 61     }
 62     for(int i=1;i<=vis1[0];i++) judge[vis1[i]]=0;//下一次不经过u及其子树,删去因此存在的路径长度 
 63     return;
 64 } 
 65 void solve(int u) //进行点分治
 66 {
 67     vis[u]=1;
 68     judge[0]=1;//解决一条路径端点分别为root与某个子节点时,这条路径可以直接充当答案,相当于与一条长为0的路径进行了第一种情况,所以长度为0的路径永远存在
 69     qiu(u);
 70     for(int i=head[u];i;i=nxt[i])
 71     {
 72         if(vis[to[i]]) continue;
 73         sum=size[to[i]];
 74         root=0;
 75         maxx[0]=2e9;//最开始设一个虚拟的点为重心 
 76         getroot(to[i],0);
 77         solve(root);
 78     }
 79     return;
 80 }
 81 int main()
 82 {
 83     scanf("%d%d",&n,&m);
 84     for(int i=1;i<n;i++)
 85     {
 86         int a,b,c;
 87         scanf("%d%d%d",&a,&b,&c);
 88         add(a,b,c);
 89         add(b,a,c);
 90     }
 91     for(int i=1;i<=m;i++) 
 92         scanf("%d",&text[i]);  //离线计算 ,保存每个k 
 93     maxx[root]=n;
 94     sum=n;
 95     getroot(1,0);  //找到整颗树的重心 
 96     solve(root);   //由整颗树的重心开始点分治 
 97     for(int i=1;i<=m;i++)
 98     {
 99         if(t[i]) printf("AYE\n");
100         else printf("NAY\n");
101     }
102     return 0;
103 } 

 

posted @ 2019-02-18 15:55  yyyr  阅读(112)  评论(0编辑  收藏  举报