点分治学习笔记

点分治学习笔记

基本思想

现在假设我们有一颗以\(root\)为根的树

考虑所有树中的路径。

我们发现:要么该路径经过\(root\)(包括端点在\(root\)上的路径),要么该路径在\(root\)的子树

那么,对于这些路径,我们只要先处理经过\(root\)的路径,对于剩下的路径,我们分别以它的孩子为根进行同样的操作即可(因为剩下的路径一定在以\(root\)为根的子树里)。这便是点分治

放个图充实版面

例题:POJ 1741 Tree

题目大意:

给你一棵树,求点对\((u,v)\)的数量,使得\(u\)\(v\)之间的距离不超过\(k\)

Solution:

考虑点分治。

对于当前树的每个节点,我们记录深度\(dep_i\)(根节点深度为\(0\)),以及除根节点以外最远的祖先\(fa_i\)

我们现在只考虑经过根节点的路径数。

于是我们得到:
\[ \sum[fa_i\ne fa_j \land dep_i+dep_j\le k] \]
其中:

\([]\)表示当表达式为真是取值为\(1\),否则为\(0\)

\(\land\) 表示逻辑与

该式等于:
\[ \sum [dep_i+dep_j \le k]-\sum[fa_i=fa_j \land dep_i+dep_j \le k] \]
显然,式子的后边可以递归分治地解决(相当于以\(fa_i\)为根)

我们只要考虑前边怎么算了。

而前面只需要一个\(two\) \(pointers\) 就解决了:

我们把所有的\(dep\)排个序,让\(l\)指向最小的,让\(r\)指向最大的那个

从左到右遍历\(l\),如果\(dep_l+dep_r\le k\),则说明对于任意满足\(l < t <= r\)\(t\),都有\(dep_l+dep_t\le k\)

即此时所有点对\((l,t)\)都符合调价。于是我们只需将答案加上\(r-l\)即可

注意:为防止题目出一条链来卡点分治,使复杂度退化为\(O(n^2)\) ,我们需要将树的重心作为根节点,以保证复杂度为\(O(n log n)\)

Q:这做法复杂度看上去像\(O(n^2)\)的啊

A:不是的。如果我们以树的重心为根节点,我们至多下降\(logn\)层,而每一层的操作是\(O(n)\)的,所以总体的时间复杂度是\(O(n log n)\)

Code:

#include<bits/stdc++.h>
using namespace std;
const int maxn=4e4+5;
struct Node{
    int to;
    int next;
    int w;
}edge[maxn<<1];
int head[maxn],cnt;
inline void add(int x,int y,int w){
    edge[++cnt].next=head[x];
    edge[cnt].to=y;
    edge[cnt].w=w;
    head[x]=cnt;
}
int n;
int K;
int size[maxn];
int f[maxn];
int ans;
int dis[maxn];
vector<int>dep;
int v[maxn];
int tot;
int root;
inline void dfs1(int x,int fa){
    size[x]=1; f[x]=0;
    for(int i=head[x];i!=0;i=edge[i].next){
        int k=edge[i].to;
        if(k==fa) continue;
        if(v[k]) continue;
        dfs1(k,x);
        size[x]+=size[k];
        f[x]=max(f[x],size[k]); 
    }
    f[x]=max(f[x],tot-f[x]);
    if(f[x]<f[root]) root=x;
}
inline void dfs2(int x,int fa){
    dep.push_back(dis[x]);
    size[x]=1;
    for(int i=head[x];i!=0;i=edge[i].next){
        int k=edge[i].to;
        int w=edge[i].w;
        if(v[k]) continue;
        if(k==fa) continue;
        dis[k]=dis[x]+w;
        dfs2(k,x);
        size[x]+=size[k];
    }
}
inline int calc(int x,int tmp){
    int res=0;
    dep.clear();
    dis[x]=tmp;
    dfs2(x,0);
    sort(dep.begin(),dep.end());
    int l=0; int r=dep.size()-1;
    while(l<r){
        if(dep[l]+dep[r]<=K) res+=r-l,l++;
        else r--;
    }
    return res;
}
inline void dfs3(int x){
    ans+=calc(x,0); 
    v[x]=1;
    for(int i=head[x];i!=0;i=edge[i].next){
        int k=edge[i].to;
        int w=edge[i].w;
        if(v[k]) continue;
        ans-=calc(k,w);
        f[0]=tot=size[k];
        dfs1(k,0);
        dfs3(k);    
    }
}
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n-1;i++){
        int x,y,w;
        scanf("%d%d%d",&x,&y,&w);
        add(x,y,w);
        add(y,x,w);
    }
    scanf("%d",&K);
    f[0]=n; tot=n;
    dfs1(1,0);
    dfs3(root);
    printf("%d\n",ans);
    return 0;
}

转载于:https://www.cnblogs.com/ybwowen/p/11000559.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
毕设新项目-基于Java开发的智慧养老院信息管理系统源码+数据库(含vue前端源码).zip 【备注】 1、该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的,请放心下载使用!有问题请及时沟通交流。 2、适用人群:计算机相关专业(如计科、信息安全、数据科学与大数据技术、人工智能、通信、物联网、自动化、电子信息等)在校学生、专业老师或者企业员工下载使用。 3、用途:项目具有较高的学习借鉴价值,不仅适用于小白学习入门进阶。也可作为毕设项目、课程设计、大作业、初期项目立项演示等。 4、如果基础还行,或热爱钻研,亦可在此项目代码基础上进行修改添加,实现其他不同功能。 欢迎下载!欢迎交流学习!不清楚的可以私信问我! 毕设新项目-基于Java开发的智慧养老院信息管理系统源码+数据库(含vue前端源码).zip毕设新项目-基于Java开发的智慧养老院信息管理系统源码+数据库(含vue前端源码).zip毕设新项目-基于Java开发的智慧养老院信息管理系统源码+数据库(含vue前端源码).zip毕设新项目-基于Java开发的智慧养老院信息管理系统源码+数据库(含vue前端源码).zip毕设新项目-基于Java开发的智慧养老院信息管理系统源码+数据库(含vue前端源码).zip毕设新项目-基于Java开发的智慧养老院信息管理系统源码+数据库(含vue前端源码).zip毕设新项目-基于Java开发的智慧养老院信息管理系统源码+数据库(含vue前端源码).zip毕设新项目-基于Java开发的智慧养老院信息管理系统源码+数据库(含vue前端源码).zip毕设新项目-基于Java开发的智慧养老院信息管理系统源码+数据库(含vue前端源码).zip
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值