算法学习分析-点分治 HDU 6269 Master of Subgraph

首先给出定义

点分治是一种处理树上路径的工具

 

挂出一道题目来:Master of Subgraph

这道题目让你求所有联通子图加和所能产生数字,问你1到m之间,那些数字可以被产生

 

这道题目,假如我们利用暴力的方法去求解的话

实际上是对每个节点进行一次dfs,这样的话会发现复杂度为O(N^2)也就是再9e6左右,再加上常数M/64,复杂度根本不够(9e9)

我们可以利用点分治去优化复杂度

点分治的原理就是树上的路径产生的答案,不是在经过这个节点的就是在不经过这个节点的,那我们找到树的重心的话,就能够算出来经过该点的所有答案,然后依次递归大约logN层,这样复杂度就变成了NlogN的程度,也就是3e4*1e3=3e7加上常数,再加上时间的宽限,完全够用

所以点分治就是树上分治的一种,减小重复计算的东西,不断逐步缩小子树的计算

 

其中一般来说要开 一个父节点数组,一个儿子数数组,来计算重心的位置

然后就是点分治,跟递归差不多就是要去计算答案

然后就是利用solve函数,每次点分治计算树根,然后依次处理子树的重心节点然后继续递归继续分治

 

  1 #include <iostream>
  2 #include <cstring>
  3 #include <vector>
  4 #include <bitset>
  5 
  6 const int MAXN=3e3+5;
  7 const int MAXM=1e5+5;
  8 
  9 int E[MAXN][MAXN];
 10 int all[MAXN];
 11 
 12 int f[MAXN],son[MAXN],root,tot;
 13 /* 
 14     分别代表f[x]的两侧孩子数目的最大值,重心的孩子数目最小
 15     son代表以x为根的孩子数目
 16     root是被移动的根
 17     tot是当前根的孩子总数
 18  */
 19 bool vis[MAXN];
 20 
 21 std::bitset<MAXM>b[MAXN],ans;
 22 
 23 int n,m,val[MAXN];
 24 
 25 void dfs(int x,int fa){
 26     /* dfs搜索树,将根移动到树的重心,降低dp层数 */
 27     f[x] = 0;
 28     /* 标记孩子数目为0 */
 29     son[x] = 1;
 30     /* 计算孩子数目 */
 31     for(int i = 1; i <= all[x]; i++)
 32     {
 33         int y=E[x][i];
 34         if(!vis[y] && y!=fa)
 35         {
 36             dfs(y,x);
 37             /* 递归进入子数 */
 38             f[x] = std::max(f[x],son[y]);
 39             /* 计算子树中孩子数目最多的子树孩子数 */
 40             son[x]+=son[y];
 41             /* 累加孩子数目 */
 42         }
 43     }
 44     f[x]=std::max(f[x],tot-f[x]);
 45     /* 计算该根节点最大孩子数其余侧的数目中两边的最大值 */
 46     if(f[x]<f[root]) root=x;
 47     /* 移动根节点,寻找重心 */
 48 }
 49 
 50 void getdp(int x, int fa){
 51     b[x]<<=val[x];
 52     /* 累加val[x] */
 53     son[x]=1;
 54 
 55 
 56     for(int i=1;i<=all[x];i++)
 57     {
 58         int y=E[x][i];
 59         if(!vis[y] && y!=fa)
 60         {
 61             b[y]=b[x];
 62             getdp(y,x);
 63             son[x]+=son[y];
 64             b[x]|=b[y];
 65         }
 66     }
 67 }
 68 
 69 void solve(int x){
 70     vis[x] = true;
 71     b[x] = 1;
 72     getdp(x,0);
 73     /* 以某一点为根进行点分治 */
 74     ans|=b[x];
 75     /* 累加答案 */
 76 
 77     for(int i=1;i<=all[x];i++)
 78     {
 79         /* 子树递归进行点分治 */
 80         int y=E[x][i];
 81         if(!vis[y])
 82         {
 83             tot = son[y];
 84             root = 0;
 85             dfs(y,x);
 86             /* 先寻找树根 */
 87             solve(root);
 88             /* 递归分治 */
 89         }
 90     }
 91 }
 92 
 93 int main(){
 94     int T;
 95     std::cin>>T;
 96     while(T--)
 97     {
 98         scanf("%d%d",&n,&m);
 99         for(int i=1;i<=n;i++) all[i]=0;
100         ans.reset();
101         memset(vis,0,sizeof(vis));
102         for(int i=1;i<n;i++)
103         {
104             int x,y;
105             scanf("%d%d",&x,&y);
106             E[x][++all[x]]=y;
107             E[y][++all[y]]=x;
108         }
109         for(int i=1;i<=n;i++)
110         {
111             scanf("%d",&val[i]);
112         }
113         
114         f[0] = n+5;
115         tot=n;
116         dfs(1,root);
117         solve(root);
118         for(int i=1;i<=m;i++)
119         {
120             printf("%d",(int)ans[i]);
121         }
122         puts("");
123     }
124     return 0;
125 }

 

转载于:https://www.cnblogs.com/rign/p/10660896.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值