树的分治之点分治

点分治我感觉是图论树部分比较考验脑力的一种题目了

POJ1741

题意:给一棵边带权树,问两点之间的距离小于等于K的点对有多少个

满足条件的点对有两种情况:两个点的路径横跨树根,两个点位于同一颗子树中

对于根节点进行一次dfs,求出deep,并将其从小到大排序

void getdeep(int x,int fa)
{
    dep[++dep[0]]=d[x];
    for(int tmp=g[x];tmp;tmp=e[tmp].next)
    {
        if(e[tmp].t==fa||vis[e[tmp].t]) continue;
        d[e[tmp].t]=d[x]+e[tmp].v;
        getdeep(e[tmp].t,x);
    }
}

然后看一下calculate

int cal(int x,int tmp)
{
    d[x]=tmp;dep[0]=0;
    getdeep(x,0);
    sort(dep+1,dep+dep[0]+1);
    int t=0,l,r;
    for(l=1,r=dep[0];l<r;)
    {
        if(dep[l]+dep[r]<=k) {t+=r-l;l++;}
        else r--;
    }
    return t;
} 

如果我们已经知道了此时所有点到根的距离a[i]

a[x] + a[y] <= k的(x, y)对数就是结果,这个可以通过排序之后O(n)的复杂度求出

然后根据分治的思想,分别对所有的儿子求一遍即可

void work(int x)  //点分治
{
    ans+=cal(x,0);
    vis[x]=1;
    for(int tmp=g[x];tmp;tmp=e[tmp].next)
    {
        if(vis[e[tmp].t]) continue;
        ans-=cal(e[tmp].t,e[tmp].v);
        sum=ch[e[tmp].t];
        root=0;
        getroot(e[tmp].t,root);
        work(root);
    }
}

但是这会出现重复的——当前情况下两个点位于一颗子树中,那么应该将其减掉

显然这两个点是满足题意的,为什么减掉呢?因为在对子树进行求解的时候,会重新计算

在进行分治时,为了避免树退化成一条链而导致时间复杂度变为O(N^2),每次都找树的重心

这样,所有的子树规模就会变的很小了。时间复杂度O(Nlog^2N)

void getroot(int x,int fa)
{
    ch[x]=1;f[x]=0;
    for(int tmp=g[x];tmp;tmp=e[tmp].next)
    {
        if(e[tmp].t==fa||vis[e[tmp].t]) continue;
        getroot(e[tmp].t,x);
        ch[x]+=ch[e[tmp].t];
        f[x]=max(f[x],ch[e[tmp].t]);
    }
    f[x]=max(f[x],sum-ch[x]);
    if(f[x]<f[root]) root=x;
}

有了板子不能救命,还要大量刷大量刷才可以,点分治确实是一个要命的地方,上次的省赛的一道题就栽了

  1 #include<cstdio>
  2 #include<algorithm>
  3 #include<cstring>
  4 using namespace std;
  5 const int maxn=10005;
  6 const int maxm=20005;
  7 const int INF=0x7fffffff;
  8 int n,k,ans;
  9 int cnt,g[maxn];
 10 struct Edge{int t,next,v;}e[maxm];
 11
 12 int root,sum;
 13 int ch[maxn],f[maxn],dep[maxn],d[maxn];
 14 bool vis[maxn];
 15
 16 void addedge(int u,int v,int w)
 17 {
 18     cnt++;
 19     e[cnt].t=v;e[cnt].v=w;
 20     e[cnt].next=g[u];g[u]=cnt;
 21 }
 22 int read()
 23 {
 24     int x=0,f=1;char ch=getchar();
 25     while(ch<‘0‘||ch>‘9‘) {if(ch==‘0‘)f=-1;ch=getchar();}
 26     while(ch>=‘0‘&&ch<=‘9‘) {x=x*10+ch-‘0‘;ch=getchar();}
 27     return x*f;
 28 }
 29 void getroot(int x,int fa)
 30 {
 31     ch[x]=1;f[x]=0;
 32     for(int tmp=g[x];tmp;tmp=e[tmp].next)
 33     {
 34         if(e[tmp].t==fa||vis[e[tmp].t]) continue;
 35         getroot(e[tmp].t,x);
 36         ch[x]+=ch[e[tmp].t];
 37         f[x]=max(f[x],ch[e[tmp].t]);
 38     }
 39     f[x]=max(f[x],sum-ch[x]);
 40     if(f[x]<f[root]) root=x;
 41 }
 42 void getdeep(int x,int fa)
 43 {
 44     dep[++dep[0]]=d[x];
 45     for(int tmp=g[x];tmp;tmp=e[tmp].next)
 46     {
 47         if(e[tmp].t==fa||vis[e[tmp].t]) continue;
 48         d[e[tmp].t]=d[x]+e[tmp].v;
 49         getdeep(e[tmp].t,x);
 50     }
 51 }
 52 int cal(int x,int tmp)
 53 {
 54     d[x]=tmp;dep[0]=0;
 55     getdeep(x,0);
 56     sort(dep+1,dep+dep[0]+1);
 57     int t=0,l,r;
 58     for(l=1,r=dep[0];l<r;)
 59     {
 60         if(dep[l]+dep[r]<=k) {t+=r-l;l++;}
 61         else r--;
 62     }
 63     return t;
 64 }
 65 void work(int x)  //点分治
 66 {
 67     ans+=cal(x,0);
 68     vis[x]=1;
 69     for(int tmp=g[x];tmp;tmp=e[tmp].next)
 70     {
 71         if(vis[e[tmp].t]) continue;
 72         ans-=cal(e[tmp].t,e[tmp].v);
 73         sum=ch[e[tmp].t];
 74         root=0;
 75         getroot(e[tmp].t,root);
 76         work(root);
 77     }
 78 }
 79 void init()
 80 {
 81     ans=root=cnt=0;
 82     memset(vis,0,sizeof(vis));
 83     memset(g,0,sizeof(g));
 84 }
 85 int main()
 86 {
 87     int x,y,z;
 88     while(1)
 89     {
 90         init();
 91         n=read();k=read();
 92         if(n==0) break;
 93         for(int i=1;i<n;i++)
 94         {
 95             x=read();y=read();z=read();
 96             addedge(x,y,z);
 97             addedge(y,x,z);
 98         }
 99         sum=n;
100         f[0]=INF;
101         getroot(1,0);
102         work(root);
103         printf("%d\n",ans);
104     }
105
106     return 0;
107 }

原文地址:https://www.cnblogs.com/aininot260/p/9461358.html

时间: 2024-10-03 22:39:13

树的分治之点分治的相关文章

COGS 2479. [HZOI 2016]偏序 [CDQ分治套CDQ分治 四维偏序]

传送门 给定一个有n个元素的序列,元素编号为1~n,每个元素有三个属性a,b,c,求序列中满足i<j且ai<aj且bi<bj且ci<cj的数对(i,j)的个数. 对于100%的数据,1<=n<=50000,保证所有的ai.bi.ci分别组成三个1~n的排列. $CDQ$分治套$CDQ$分治也不是很难嘛 对于本题,设四维$a,b,c,d$ $Sort\ at\ a$ $CDQ(l,r)$ $\quad CDQ(l,mid)$ $\quad CDQ(mid+1,r)$ $\

点分治+动态点分治

最近好颓废,什么都学不进去... 感谢两篇:AKMer - 浅谈树分治  言简意赅 LadyLex - 点分治&动态点分治小结  讲解+例题,学到很多东西 点分治 动态点分治 ~ 点分治 ~ 经常遇见一类树上的计数题,问的是在某些条件下,选择一些点的方案数 若对于每个点的统计都需要遍历以其为根节点的子树,普通的做法就是$O(n^2)$的,在很多时候是不满足要求的 而这是点分治的专长 点分治是这样进行的: 1. 找到当前树的重心 2. 将重心及重心连出的边全部删去,那么就能将原来的树分割成森林 3

POJ 2299 Ultra-QuickSort (树状数组or 归并排序分治求逆序对数)

题目大意就是说帮你给一些(n个)乱序的数,让你求冒泡排序需要交换数的次数(n<=500000) 显然不能直接模拟冒泡排序,其实交换的次数就是序列的逆序对数. 由于数据范围是 0 ≤ a[i] ≤ 999,999,999所以先要离散化,然后用合适的数据结果求出逆序 可以用线段树一步一步添加a[i],每添加前查询前面添加比它的大的有多少个就可以了. 也可用树状数组,由于树状数组求的是(1...x)的数量和所以每次添加前查询i-sum(a[i])即可 树状数组: //5620K 688MS #incl

POJ 1741 树分治(点分治模板题)

POJ 1741 题意:求一棵树中点对<=k的数量. 总结:点分治,搞不太懂..大概敲了一遍 #include<iostream> #include<cstdio> #include<cstdlib> #include<algorithm> #include<cstring> #include<string> #include<cmath> #include<queue> #include<stac

BZOJ 4016: [FJOI2014]最短路径树问题( 最短路 + 点分治 )

先跑出最短路的图, 然后对于每个点按照序号从小到大访问孩子, 就可以搞出符合题目的树了. 然后就是经典的点分治做法了. 时间复杂度O(M log N + N log N) ---------------------------------------------------------------------------- #include<queue> #include<cctype> #include<cstdio> #include<cstring>

POJ 1741 Tree 树分治(点分治)

题意:给你一颗带边权树,问你其中 dis(v,u) <= k 的对数 解题思路: 首先推荐大家看 09年国家集训队漆子超 的论文 看到这题  我们可以有三种思路 第一种是枚举起点,遍历整颗树找对数    时间复杂度 为  O(n^2),空间复杂度为 O(n) 第二种是树形dp的思想     每个节点用 长度为 K 数组维护 ,递归求解  时间复杂度为 O(n ^k)空间复杂度 为 O(n) 第三种就是我们要用到的点分治的思想. 这种思想的具体思路是  先找到一个  根  对这个根进行 深搜, 找

关于树论【动态点分治】

搬运:题意传送门:http://caioj.cn/problem.php?id=1433 前几天跟波* * * *老师一起搞这题,结果最后莫名其妙的被波老师D飞... 我用到的是动态点分治. 动态点分治就是基于树的重心(一棵树中,以重心为根的最大子树的节点最小)上的解法,该解法将一棵树分成若干棵子树,对每一棵子树进行重新分配,一直往下分直到每一颗树的节点数为1,上一层的重心需要连接下一层的重心,对这棵新树来进行分治递归求解. 这道题的做法: 对于新树每个节点我们用两个堆来维护,每个节点的第一个堆

牛客练习赛11 B trie树+拓扑判环 E 分治求平面最近点对

牛客练习赛11 B  假的字符串题意:给定n个字符串,互不相等,你可以任意指定字符之间的大小关系(即重定义字典序),求有多少个串可能成为字典序最小的串,并输出它们. tags:好题 对于一个字符串, 1]如有其它字符串是它的前缀,那肯定不可能.这个直接用字典树处理就可以. 2]但如果以这个字符串为最小,怎么判定其它字符串不会矛盾呢? 其实矛盾的情况详细一点说是: 比如要以  abcd 为最小, 但又有另一个字符串 aba ,这就矛盾了. 对这种情况,在跑字典树的时候,我们对有相同父亲结点的多个儿

[TS-A1486][2013中国国家集训队第二次作业]树[树的重心,点分治]

首先考虑暴力,可以枚举每两个点求lca进行计算,复杂度O(n^3logn),再考虑如果枚举每个点作为lca去枚举这个点的子树中的点复杂度会大幅下降,如果我们将每个点递归考虑,每次计算过这个点就把这个点删掉,那么如果每次删掉的点是当前子树的重心,枚举点对的复杂度就只有O(logn),对于每次查询答案在trie树中找到时间复杂度基本可视为O(1),最后在分治同时考虑“关键点”即可.总复杂度O(nlogn). 1 #include <iostream> 2 #include <algorith