首页 > 其他分享 >点分治学习笔记

点分治学习笔记

时间:2023-05-02 16:22:43浏览次数:45  
标签:cnt dist int siz 分治 笔记 学习 maxsiz root

点分治

序列上的操作可以用很多方式维护,线段树树状数组平衡树啥的,但是如果毒瘤出题人把序列搬到了树上,则需要一些奇妙方法。一般有两种(好几种?)方法:

  1. 树链剖分,把树上路径转化成 dfn 序上的多段进行操作。
  2. LCT,不多说,目前只会板子,没搞太懂。
  3. 点分治,这个是不用把树转化成序列的,而是将树上统计(一般为统计)进行一个分治。
  4. ......

接下来重点讲解第三种。

概述

在统计某类符合要求的路径数目时,可以将这种路径分为 2 类:

  1. 在根节点的各个儿子节点的子树内,不经过根节点。
  2. 经过当前选择的根节点。

对于第一种情况,我们可以直接把它分治到各个儿子节点的子树中,让它们自己去统计。

而对于第二种情况,我们可以把一条路径拆成两部分:到根节点和从根节点出发。这样每颗子树内部统计到根节点的路径,最后再合并即可。

时间复杂度分析

这块也是我这个蒟蒻刚刚稍微想明白了一点的

每次 dfs 一遍,差不多每经过一层会把整棵树遍历一遍,于是时间复杂度就取决于层数的多少。

当遇到数据构造成一条链时,时间复杂度可以卡到 \(O(n^2)\),不能承受,因此我们需要尽可能减少树的层数,于是每次递归之前先找一下子树的重心,从重心再开始点分治,这样时间复杂度就能达到 \(O(n\log n)\)。

例题

P3806 【模板】点分治1

本题要统计树上是否存在距离为 \(k\) 的点对,我们可以把一条路径 \((u,v)\) 拆成 \((u, root)\) 和 \((root, v)\),如果我们已经有了 \(dist_{u, root}\),就可以在一个桶里查找是否存在一条路径 \((root, v)\) 使得 \(dist_{root, v} = k - dist_{u, root}\)。

同时向下递归之前要清空桶,这里不能直接 memset,会炸掉,于是用一个队列记录有多少点被改变过,最后只把这些点变为 0 即可。

代码:

#include <bits/stdc++.h>
using namespace std;

const int N = 1e6 + 10, INF = 0x3f3f3f3f;
int n, m, k;
int h[N], e[N], w[N], ne[N], idx;
int dist[N];
int siz[N], maxsiz[N];
int q[N];
bool tf[N * 10];
bool vis[N], res[N];
int rt;

inline void add(int a, int b, int c)
{
    e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx ++;
}

void calc_siz(int u, int fa, int sz)
{
    siz[u] = 1;
    maxsiz[u] = 0;
    for(int i = h[u]; i != -1; i = ne[i])
    {
        int j = e[i];
        if(j == fa || vis[j]) continue;
        calc_siz(j, u, sz);
        siz[u] += siz[j];
        maxsiz[u] = max(maxsiz[u], siz[j]);
    }
    maxsiz[u] = max(maxsiz[u], sz - siz[u]);
    if(maxsiz[u] < maxsiz[rt])
    {
        rt = u;
    }
}

int dd[N], tt;
void calc_dist(int u, int fa)
{
    dd[++ tt] = dist[u];
    for(int i = h[u]; i != -1; i = ne[i])
    {
        int j = e[i];
        if(j == fa || vis[j]) continue;
        dist[j] = dist[u] + w[i];
        calc_dist(j, u);
    }
}

queue<int> tag;
void dfz(int x, int fa)
{
    tf[0] = true;
    tag.push(0);
    vis[x] = true;
    for(int i = h[x]; i != -1; i = ne[i])
    {
        int j = e[i];
        if(j == fa || vis[j]) continue;
        dist[j] = w[i];
        calc_dist(j, x);
        for(int k = 1; k <= tt; k ++ )
            for(int u = 1; u <= m; u ++ )
                if(q[u] >= dd[k]) res[u] |= tf[q[u] - dd[k]];
        for(int k = 1; k <= tt; k ++ )
            if(dd[k] < 10000000)
            {
                tf[dd[k]] = true;
                tag.push(dd[k]);
            }
        tt = 0;
    }
    while(!tag.empty()) tf[tag.front()] = false, tag.pop();
    for(int i = h[x]; i != -1; i = ne[i])
    {
        int j = e[i];
        if(j == fa || vis[j]) continue;
        rt = 0;
        maxsiz[rt] = INF;
        calc_siz(j, x, siz[j]);
        calc_siz(rt, -1, siz[j]);
        dfz(rt, x);
    }
}

int main()
{
    memset(h, -1, sizeof h);

    n = read(), m = read();
    for(int i = 1; i < n; i ++ )
    {
        int a = read(), b = read(), c = read();
        add(a, b, c), add(b, a, c);
    }

    for(int i = 1; i <= m; i ++ )
        q[i] = read();

    rt = 0;
    maxsiz[rt] = INF;
    siz[1] = n;
    calc_siz(1, -1, n);
    calc_siz(rt, -1, n);
    dfz(rt, -1);
    for(int i = 1; i <= m; i ++ )
        if(res[i])
            puts("AYE");
        else puts("NAY");
    
    return 0;
}

P4178 Tree

本题和上面差不多,只是多了查询点对的数量,因此用平衡树维护即可。


#include <bits/stdc++.h>
using namespace std;

const int N = 1e6 + 10, INF = 0x3f3f3f3f;
int h[N], e[N], ne[N], w[N], idx;
int maxsiz[N], siz[N], dist[N];
bool vis[N];
int rt;
int n, k;

struct fhq
{
    struct node
    {
        int l, r;
        int val, key;
        int siz;
    }t[N];
    int root, cnt;

    inline int New(int val)
    {
        t[++ cnt].val = val;
        t[cnt].key = rand();
        t[cnt].siz = 1;
        t[cnt].l = t[cnt].r = 0;
        return cnt;
    }

    inline void pushup(int p)
    {
        t[p].siz = t[t[p].l].siz + t[t[p].r].siz + 1;
    }

    int merge(int x, int y)
    {
        if(!x || !y) return x + y;
        if(t[x].key > t[y].key)
        {
            t[x].r = merge(t[x].r, y);
            pushup(x);
            return x;
        }
        else
        {
            t[y].l = merge(x, t[y].l);
            pushup(y);
            return y;
        }
    }

    void split(int p, int val, int &x, int &y)
    {
        if(!p) x = y = 0;
        else
        {
            if(t[p].val <= val)
            {
                x = p;
                split(t[p].r, val, t[x].r, y);
            }
            else
            {
                y = p;
                split(t[p].l, val, x, t[y].l);
            }
            pushup(p);
        } 
    }

    int x, y, z;
    inline void insert(int val)
    {
        split(root, val, x, y);
        root = merge(merge(x, New(val)), y);
    }

    inline void clear()
    {
        cnt = root = 0;
    }

    inline int lowernums(int val)
    {
        split(root, val, x, y);
        int res = t[x].siz;
        root = merge(x, y);
        return res;
    }
} t;

inline void add(int a, int b, int c)
{
    e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++;
}

void calc_siz(int u, int fa, int sz)
{
    siz[u] = 1;
    maxsiz[u] = 0;
    for(int i = h[u]; i != -1; i = ne[i])
    {
        int j = e[i];
        if(j == fa || vis[j]) continue;
        calc_siz(j, u, sz);
        siz[u] += siz[j];
        maxsiz[u] = max(maxsiz[u], siz[j]);
    }
    maxsiz[u] = max(maxsiz[u], sz - siz[u]);
    if(maxsiz[u] < maxsiz[rt])
    {
        rt = u;
    }
}

int dd[N], tt;
void calc_dis(int x, int fa)
{
    dd[++ tt] = dist[x];
    for(int i = h[x]; i != -1; i = ne[i])
    {
        int j = e[i];
        if(j == fa || vis[j]) continue;
        dist[j] = dist[x] + w[i];
        calc_dis(j, x);
    }
}

ll ans;
void dfz(int u, int fa)
{
    vis[u] = true;
    t.insert(0);
    for(int i = h[u]; i != -1; i = ne[i])
    {
        int j = e[i];
        if(j == fa || vis[j]) continue;
        dist[j] = w[i];
        calc_dis(j, u);
        for(int i = 1; i <= tt; i ++ )
            ans += t.lowernums(k - dd[i]);
        for(int i = 1; i <= tt; i ++ )
            t.insert(dd[i]);
        tt = 0;
    }
    t.clear();
    for(int i = h[u]; i != -1; i = ne[i])
    {
        int j = e[i];
        if(j == fa || vis[j]) continue;
        rt = 0;
        maxsiz[rt] = INF;
        calc_siz(j, u, siz[j]);
        calc_siz(rt, -1, siz[j]);
        dfz(rt, u);
    }
}

int main()
{
    memset(h, -1, sizeof h);

    n = read();
    for(int i = 1; i < n; i ++ )
    {
        int a = read(), b = read(), c = read();
        add(a, b, c), add(b, a, c);
    }

    k = read();
    rt = 0;
    maxsiz[rt] = INF;
    calc_siz(1, -1, n);
    calc_siz(rt, -1, n);
    dfz(rt, -1);

    cout << ans << endl;
    
    return 0;
}

后记

其实点分治我还没有完全搞明白,现在做题也是大部分都是一头雾水,也因此只放了 2 道例题(能力有限),而且点分树什么的也没学,所以只能等以后再补充。

标签:cnt,dist,int,siz,分治,笔记,学习,maxsiz,root
From: https://www.cnblogs.com/crimsonawa/p/17367844.html

相关文章

  • Qt 学习笔记
     1.  *newClass 与引用<qpushbutton.cpp>:QPushButton::QPushButton(QWidget*parent):QAbstractButton(*newQPushButtonPrivate,parent){Q_D(QPushButton);d->init();}<qabstractbutton.cpp>:/*!\internal*/对应的函数原型......
  • vue学习 第七天 清除浮动 (clear:xxx)
    清除浮动 问题一、父元素不方便设置高度,子元素设置浮动(不占位置),父元素的高度会默认为0,就会影响下面的标准流的盒子。总结:子盒子浮动,父盒子失去高度,影响了整体布局1、清除浮动的原因由于浮动元素不再占用原文档流的......
  • IT工具知识-18: ADB操作笔记(自用)
    Linux下的常用命令(持续更新)终端使用bashshell查询安卓设备当前活动的APP包名和活动窗口名adbshelldumpsyswindowwindows|grep-E'mCurrentFocus|mFocusedApp'启动指定app下的指定窗口app包名和活动窗口名都要提供,否则无法启动adbshellamstart包名/活动窗口......
  • vue学习 第六天 浮动 (float) 和 页面传统布局(标准流、浮动、定位)。
    浮动(float)1、传统网页布局的三种方式(3种)网页布局的本质---用CSS来摆放盒子。把盒子摆放到相应位置。CSS提供了三种传统布局方式(盒子如何进行排列顺序):普通流(标准流)、浮动、定位2、标准流(普通流/文档流)就是标签按照规定好默认方式排......
  • 联邦学习基础
    作者:程勇链接:https://zhuanlan.zhihu.com/p/87858287来源:知乎著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。联邦学习(FederatedLearning,a.k.a.FederatedMachineLearning)可以分为三类:横向联邦学习(HorizontalFederatedLearning),纵向联邦学习(Vertical......
  • vue学习 第五天 css基础 --- ps操作 / 圆角边框(border-radius) / 阴影(盒子/文字)b
      ps基本操作1、ps的基本操作2、ps快捷操作的位置3、样式书写习惯 4、样式设置的小细节(注意)1、图片设置width:100%这样图片的宽度就不会超过父容器的宽度。2、块元素没有设置宽度,给margin左右是没有效果的。......
  • Redis 是在CentOS 5.7上学习入门文章起步
    Redis是在CentOS5.7上学习入门文章起步  Rdis和JQuery一样是纯粹为应用而产生的,这里记录:1.Redis简介Redis是一个key-value存储系统。和Memcached类似,但是解决了断电后数据完全丢失的情况,而且她支持更多无化的value类型,除了和string外,还支持lists(链表)、sets(集合)和zsets(有......
  • 学习jdbc时遇到的问题
    jar包问题问题描述:java.sql.SQLException:Unabletoloadauthenticationplugin'caching_sha2_password”如果是上述的问题,可能就是jar包的问题我的Mysql是8.0.26的,而我所用的Java包时MySQL5的Java包,这时只要把jar包更改为MySQL8的jar包即可解决问题成功使用......
  • 四月读书笔记
    梦断代码这本书让我越发意识到作为软件开发者的不容易。程序员都怀揣着成就一番事业的心,他们信心满满,斗志昂扬,但因为种种私人原因不能够与其他程序员很好的合作,团队精神难以成型。作为乐观主义者,他们不畏惧任何困难,正因如此,才为计算机提供了无尽的可能目标要实际。实际这个词其实......
  • 医学图像的深度学习的完整代码示例:使用Pytorch对MRI脑扫描的图像进行分割
    图像分割是医学图像分析中最重要的任务之一,在许多临床应用中往往是第一步也是最关键的一步。在脑MRI分析中,图像分割通常用于测量和可视化解剖结构,分析大脑变化,描绘病理区域以及手术计划和图像引导干预,分割是大多数形态学分析的先决条件。本文我们将介绍如何使用QuickNAT对人脑的......