点分治模板——luogu P3806【模板】点分治1、P4178 Tree、P2634[国家集训队] 聪明可可

本文深入探讨了树的分治算法,特别是点分治策略,讲解如何选取树的重心作为根节点以优化递归深度,减少算法复杂度。并通过具体实例解析树上点对问题的解决方法,提供P3806、CF161D和P2634等题目的参考程序。

树的分治算法常见两种,一是点分治,二是边分治,本文只考虑点分治。
点分治,顾名思义,首先选取一个点将无根树转换为有根树,再递归处理每一棵以根节点的儿子为根的子树。
对于树的分治算法来说,递归的深度往往决定着算法效率的高低,所以,该如何选取这个根节点呢?最坏的情况,树退化成链,选取的根节点是链头,时间复杂度O(n)。所以,我们选取的根节点要保证最大的子树最小,也就是选择树的重心作为根节点。递归深度最坏为O(logn)
考虑树上点对的问题,例题求树上是否存在两点满足距离等于k,点数n<=1e4, 询问次数m<=1e2
已知当前根节点为r,路径存在两种情况:
1.经过r
2.被r的某一棵子树包含,不经过r
对于第二种情况的路径又可以进行递归处理,看作是子问题进行分治,变成第一种情况。
所以只用考虑第一种路径的计算。
假设有点x、y分属于不同的子树,路径(x,y)看作(x,r)+(r,y),通过dfs()计算两点到根的距离,即判断dis[x]+dis[y]==k。为了防止误判第二种情况,记录x、y所属的子树根节点sub_root[],求dis[x]+dis[y] == k && sub_root[x] != sub_root[y] 。计算过程见Calc()部分
处理点对后删除当前根节点(打标记),对所有子树递归处理。

下附P3806参考程序

//P3806
#include <bits/stdc++.h>
using namespace std;
const int maxn = 10005;
const int maxm = 105;
struct E
{
    int v,w,ne;
}edge[maxn<<1];
int tot = 1,head[maxn];
int n,m,k[maxm];
bool ok[maxm];
void add(int u,int v,int w)
{
    edge[++tot] = (E){v,w,head[u]};
    head[u] = tot;
}
void read()
{
    int u,v,w;
    scanf("%d%d",&n,&m);
    for(int i=1;i<n;i++)
    {
        scanf("%d%d%d",&u,&v,&w);
        add(u,v,w);
        add(v,u,w);
    }
    for(int i=1;i<=m;i++)
    {   scanf("%d",&k[i]);
        if(!k[i])
            ok[i] = true;
    }
}

bool vis[maxn],w[maxn];
int sz[maxn],pos,ans;
long long dis[maxn];//不开ll也没问题,最坏情况退化成链max(n)*max(w) = 1e8
int sub_root[maxn],sub_node[maxn],total_node = 0;
void dfs_root(int x,int S)//重心
{
    int y,max_part = 0;
    sz[x] = 1,vis[x] = 1;
    for(int i=head[x];i;i=edge[i].ne)
    {
        y = edge[i].v;
        if(vis[y] || w[y])  continue;
        dfs_root(y,S);
        sz[x]+=sz[y];
        max_part = max(max_part,sz[y]);
    }
    max_part = max(max_part,S-sz[x]);
    if(max_part < ans)
    {
        ans = max_part;
        pos = x;
    }
}
void dfs(int x)//到根节点的距离
{
    int y,z;
    vis[x] = 1;
    for(int i=head[x];i;i=edge[i].ne)
    {   y = edge[i].v, z = edge[i].w;
        if(vis[y] || w[y])  continue;
        dis[y] = dis[x]+z;
        sub_node[++total_node] = y;
        sub_root[y] = sub_root[x];
        dfs(y);
    }
}
bool cmp(int x,int y)
{
    return dis[x] < dis[y];
}
void work(int x,int S)
{   ans = S;
    memset(vis,0,sizeof(vis));
    dfs_root(x,S);//找重心pos

    memset(vis,0,sizeof(vis));
    memset(dis,0,sizeof(dis));
    total_node = 0;
    sub_root[pos] = sub_node[++total_node] = pos;
    w[pos] = 1;
    for(int i=head[pos];i;i=edge[i].ne)//处理以根的儿子为根的子树
    {   register int y = edge[i].v,z = edge[i].w;
        if(vis[y] || w[y])  continue;
        dis[y] = z;
        sub_root[y] = y,sub_node[++total_node] = y;
        dfs(y);
    }
//Calc(),判断所有的k,用二分查找速度会更快
    sort(sub_node+1,sub_node+total_node+1,cmp);//按照点到子树根的距离排序
    for(int i=1;i<=m;i++)
    {
        if(ok[i])   continue;
        int l = 1,r = total_node;
        while(l<r)
        {   if(dis[sub_node[r]]>k[i])
        		r--;
            else if(dis[sub_node[l]]+dis[sub_node[r]] < k[i])
                l++;
            else if(dis[sub_node[l]]+dis[sub_node[r]] > k[i])
                r--;
            else if(sub_root[sub_node[l]] == sub_root[sub_node[r]])//距离之和相同,但是属于同一颗子树
            {
                if(dis[sub_node[r]] == dis[sub_node[r-1]])  r--;
                else                                        l++;
            }
            else
            {
                ok[i] = true;
                break;
            }
        }
    }
//分治
    for(int i=head[pos];i;i=edge[i].ne)
    {
        if(w[edge[i].v])    continue;
        work(edge[i].v,sz[edge[i].v]);
    }
}
int main()
{
    //freopen("test.in","r",stdin);
    read();
    work(1,n);
    for(int i=1;i<=m;i++)
    {
        printf("%s\n",ok[i]?"AYE":"NAY");
    }
    return 0;
}

CF161D 计算有多少个点对距离是k
k值很小,采用桶记录

#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn = 50005;
struct E
{
    int v,w,ne;
}edge[maxn<<1];
int n,k,tot = 1,head[maxn];
void add(int u,int v)
{
    //edge[++tot] = (E){v,1,head[u]};
    edge[++tot].v =v,edge[tot].w = 1,edge[tot].ne=head[u];
    head[u] = tot;
}
void read()
{   int x,y;
    scanf("%d%d",&n,&k);
    for(int i=1;i<n;i++)
    {
        scanf("%d%d",&x,&y);
        add(x,y);
        add(y,x);
    }
}
int sz[maxn],ans,pos;
bool v[maxn],w[maxn];
void find_root(int x,int S)
{
    int y,max_part = 0;
    v[x] = 1;
    sz[x] = 1;
    for(int i=head[x];i;i=edge[i].ne)
    {
        y = edge[i].v;
        if(v[y] || w[y])    continue;
        find_root(y,S);
        sz[x]+=sz[y];
        max_part = max(max_part,sz[y]);
    }
    max_part = max(max_part,S-sz[x]);
    if(max_part < ans)
    {
        ans = max_part;
        pos = x;
    }
}
int dis[maxn];
ll ANS;
ll bucket[505];
void dfs(int x)
{   int y;
    if(dis[x] > k)    return;
    bucket[dis[x]]++;//桶排序
    v[x] = 1;
    //printf("(%d)",x);
    for(int i=head[x];i;i=edge[i].ne)
    {
        y = edge[i].v;
        if(v[y] || w[y])    continue;
        dis[y] = dis[x] + 1;
        dfs(y);
    }
}
ll calc()
{   /*for(int i=0;i<=k;i++)
    {
        printf("%lld ",bucket[i]);
    }
    cout << endl;*/
    ll ret=0;
    for(int i = 0;i <= k;i++)
    {
        if(i >= k-i) break;
        ret+=bucket[i]*bucket[k-i];
    }
    if(k%2==0)//偶数
    {
        ret+=(bucket[k/2]*(bucket[k/2]-1))/2;
    }
    return ret;
}
void work(int x,int S)
{   ans = S;
    memset(v,0,sizeof(v));
    find_root(x,S);

    memset(v,0,sizeof(v));
    memset(dis,0,sizeof(dis));
    memset(bucket,0,sizeof(bucket));
    w[pos] = true;
    dfs(pos);
    //ANS+=calc();
    //cout << pos << ":";
    ll tmp = calc();
    ANS+=tmp;
    memset(dis,0,sizeof(dis));
    memset(v,0,sizeof(v));
    for(int i=head[pos];i;i=edge[i].ne)
    {
        int y = edge[i].v;
        if(v[y] || w[y])    continue;
        dis[y] = 1;
        memset(bucket,0,sizeof(bucket));
        dfs(y);
        //ANS-=calc();//减去子树内的重复计算
       // cout << y << ":";
        tmp = calc();
        ANS-=tmp;
    }

    for(int i=head[pos];i;i=edge[i].ne)
    {
        int y = edge[i].v;
        if(w[y])    continue;
        work(y,sz[y]);
    }
}
int main()
{
    read();
    work(1,n);
    printf("%I64d",ANS);
    return 0;
}

P2634 【国家集训队】聪明可可
除了用容斥原理两次dfs减去重复计算或者是标记根节点直接减去的方法,也可以考虑根据递归的顺序,记录答案= 当前子树x已经遍历的子树,从而避免重复计算第二种路径。
符合要求的路径一定是端点在以根节点儿子为根的不同子树里,遍历时按照子树顺序依次遍历。
用disx[]数组记录下之前已经遍历的子树结果,disy[]记录当前遍历的子树,计算答案ANS+=disx[]*disy[]
最后把当前子树的遍历结果合并入已经遍历的子树结果数组disx[].

#include <bits/stdc++.h>
using namespace std;
const int maxn = 2e4+5;
struct E
{
    int v,w,ne;
}edge[maxn<<1];
int tot = 1, head[maxn];
int n;
int gcd(int x,int y)
{
    return y==0?x : gcd(y,x%y);
}
void add(int u,int v,int w)
{
    edge[++tot] = (E){v,w,head[u]};
    head[u] = tot;
}
void read()
{   int u,v,w;
    scanf("%d",&n);
    for(int i=1;i<n;i++)
    {
        scanf("%d%d%d",&u,&v,&w);
        add(u,v,w);
        add(v,u,w);
    }
}
int sz[maxn],pos,ans;
bool vis[maxn],w[maxn];
void find_root(int x,int S)
{
    int y,max_part = 0;
    sz[x] = vis[x] = 1;
    for(int i=head[x];i;i=edge[i].ne)
    {
        y = edge[i].v;
        if(vis[y] || w[y])  continue;
        find_root(y,S);
        sz[x]+=sz[y];
        max_part = max(max_part,sz[y]);
    }
    max_part = max(max_part,S-sz[x]);
    if(max_part < ans)
    {
        ans = max_part;
        pos = x;
    }
}
int ANSX=0,ANSY=0,disx[3],disy[3];
void dfs(int x,int dist)
{   int y;
    vis[x] = 1;
    disy[dist]++;
    for(int i=head[x];i;i=edge[i].ne)
    {
        y = edge[i].v;
        if(vis[y] || w[y])  continue;
        dfs(y,(dist+edge[i].w)%3);
    }
}
void work(int x,int S)
{
    ans = S;
    memset(vis,0,sizeof(vis));
    find_root(x,S);
    w[pos] = 1;
    memset(vis,0,sizeof(vis));
    disx[0] = disx[1] = disx[2] = 0;
    disy[0] = disy[1] = disy[2] = 0;
    int y;
    for(int i=head[pos];i;i=edge[i].ne)
    {   y = edge[i].v;
        if(vis[y] || w[y])  continue;
        dfs(y,edge[i].w%3);
        ANSX += disx[0]*disy[0]*2 + disx[1]*disy[2]*2 + disx[2]*disy[1]*2+disy[0]*2;//disy[0]:根节点出发长度为3
        for(int j=0;j<3;j++)
            disx[j]+=disy[j],disy[j] = 0;
    }
    for(int i = head[pos];i;i=edge[i].ne)
    {   y = edge[i].v;
        if(w[y])    continue;
        work(y,sz[edge[i].v]);
    }
}
int main()
{   freopen("test.in","r",stdin);
    read();
    work(1,n);
    ANSX += n;//两点重合
    ANSY = n*n;
    int tmp = gcd(ANSX,ANSY);
    printf("%d/%d",ANSX/tmp, ANSY/tmp);   
    return 0;
}
本设计项目聚焦于一款面向城市环保领域的移动应用开发,该应用以微信小程序为载体,结合SpringBoot后端框架与MySQL数据库系统构建。项目成果涵盖完整源代码、数据库结构文档、开题报告、毕业论文及功能演示视频。在信息化进程加速的背景下,传统数据管理模式逐步向数字化、系统化方向演进。本应用旨在通过技术手段提升垃圾分类管理工作的效率,实现对海量环保数据的快速处理与整合,从而优化管理流程,增强事务执行效能。 技术上,前端界面采用VUE框架配合layui样式库进行构建,小程序端基于uni-app框架实现跨平台兼容;后端服务选用Java语言下的SpringBoot框架搭建,数据存储则依托关系型数据库MySQL。系统为管理员提供了包括用户管理、内容分类(如环保视频、知识、新闻、垃圾信息等)、论坛维护、试题与测试管理、轮播图配置等在内的综合管理功能。普通用户可通过微信小程序完成注册登录,浏览各类环保资讯、查询垃圾归类信息,并参与在线知识问答活动。 在设计与实现层面,该应用注重界面简洁性与操作逻辑的一致性,在满足基础功能需求的同时,也考虑了数据安全性与系统稳定性的解决方案。通过模块化设计与规范化数据处理,系统不仅提升了管理工作的整体效率,也推动了信息管理的结构化与自动化水平。整体而言,本项目体现了现代软件开发技术在环保领域的实际应用,为垃圾分类的推广与管理提供了可行的技术支撑。 资源来源于网络分享,仅用于学习交流使用,请勿用于商业,如有侵权请联系我删除!
内容概要:本文系统介绍了敏捷开发在汽车电子架构设计中的应用背景、核心理念及其相较于传统瀑布式开发的优势。文章从传统开发流程存在的问题切入,阐述了敏捷开发兴起的原因,并深入解析《敏捷软件开发宣言》提出的四大价值观:个体和互动高于流程和工具、工作的软件高于详尽的文档、客户合作高于合同谈判、响应变化高于遵循计划。重点强调敏捷开发以迭代为核心实践方式,通过小步快跑、持续交付可运行软件、频繁获取反馈来应对需求变化,提升开发效率与客户价值。同时展示了敏捷开发在互联网和汽车行业的广泛应用,如苹果、谷歌、博世、奔驰等企业的成功实践,证明其在智能化转型中的普适性和有效性。; 适合人群:从事汽车电子、嵌入式系统开发的工程师,以及对敏捷开发感兴趣的项目经理、产品经理和技术管理者;具备一定软件开发背景,希望提升开发效率和团队协作能力的专业人士。; 使用场景及目标:①理解敏捷开发相对于传统瀑布模型的核心优势;②掌握敏捷开发四大价值观的内涵及其在实际项目中的体现;③借鉴行业领先企业的敏捷转型经验,推动团队或组织的敏捷实践;④应用于智能汽车、车联网等快速迭代系统的开发流程优化。; 阅读建议:此资源侧重理念与实践结合,建议读者结合自身开发流程进行对照反思,在团队中推动敏捷思想的落地,注重沟通协作机制建设,并从小范围试点开始逐步实施迭代开发。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值