POJ 1741 Tree 树的分治

本文介绍了一种使用树形动态规划解决特定问题的方法,并通过优化策略避免超时问题,尤其是在处理树状结构的数据时。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目大意:给你一棵树,边有权值,问距离不大于m的点有多少对。


男人八题之五。。果然霸气。。

最初的思路自然是O(n^2)求每对点的距离然后统计,不过这肯定会超时的。Pass。

然后就用树形DP的思路了,

选一个点为根节点向下递归,

每递归到一点p,

求都在以p为根的子树上,且不在同一以p的子节点为根的子树上且距离小于m的点对数。(也就是 最小公共祖先为p 且 距离小于m 的点对数)

然后相加得到答案。


不过这还是会TLE的...额...

在特殊数据下,比如说一条单链,上面的做法其实还是O(n^2)的复杂度,

所以要进行优化。

在每枚举到一点p时,可以看到,p往上的那些点和目前要求解的东西已经没关系了,

所以我们目前可以把以p为根的子树单独拿出来考虑。

然后现在就是求在这课子树上所有的距离小于m的点对数了,

然后为了防止出现类似单链的那种情况,

在把子树分离出来以后我们需要对子树重新定义一个根,

尽可能使得子树的最大深度最小。

比如原先 a - a - a - a - a 的一条单链,

如果以第一个a为根我们完全类似于暴力求解了,

但是以第三个a为根遍会节省许多时间。

求出a后再以上面的方法求解就行了。


说这么简单,但是真实实现时候还是有点麻烦的。

看代码吧 

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define N 10010
#define M 20010
int m;
int head[N],ip;
bool visit[N];
int dis[N];
int f[N];
int ans;
int num;
struct
{
    int son;
    int maxn;//该店往下的最大深度
}node[N];
struct
{
    int to;
    int c;
    int next;
}edge[M];

void addedge(int u,int v,int c)
{
    edge[ip].to=v;
    edge[ip].c=c;
    edge[ip].next=head[u];
    head[u]=ip++;
}

void dfs(int pos,int fa)//求每个点的子节点数以及该店往下的最大深度
{
    int t;
    f[num++]=pos;
    node[pos].son=1;
    node[pos].maxn=0;
    for(int p=head[pos];p!=-1;p=edge[p].next)
    {
        if(edge[p].to==fa||visit[edge[p].to])  continue;
        dfs(edge[p].to,pos);
        node[pos].son+=node[edge[p].to].son;
        if(node[edge[p].to].son>node[pos].maxn)
        {
            node[pos].maxn=node[edge[p].to].son;
        }
    }
}

int getroot(int pos,int fa)//找一个点为根使得该子树中的最大深度最小
{
    num=0;
    dfs(pos,fa);
    int root,root1=1e9;
    int temp;
    for(int i=0;i<num;i++)
    {
        temp=max(node[f[i]].maxn,node[pos].son-node[f[i]].son);
        if(temp<root1)
        {
            root=f[i];
            root1=temp;
        }
    }
    //其实当前求得的根并不一定使得最大深度最小
    //因为node[pos].son-node[f[i]].son并不是从pos网上的最大深度
    //但是也能求一下相对最优解,暂且这么用吧,影响不大
    return root;
}

void getdis(int pos,int fa,int len)
{
    dis[num++]=len;
    for(int p=head[pos];p!=-1;p=edge[p].next)
    {
        if(edge[p].to==fa||visit[edge[p].to])  continue;
        getdis(edge[p].to,pos,len+edge[p].c);
    }
}

int  count()//这个统计有个小技巧,把原先O(n^2)简化为O(n),自己可以手酸模拟一下,挺简单的
{
    sort(dis,dis+num);
    int l=0,r=num-1;
    int ret=0;
    while(l<r)
    {
        if(dis[l]+dis[r]<=m)
        {
            ret+=r-l;
            l++;
        }
        else r--;
    }
    return ret;
}

void solve (int pos,int fa)
{
    int root=getroot(pos,fa);//把原先以pos为根的子树分离出来,然后重新把该子树的根定义为root求解
    visit[root]=1;//之前访问过的点标记,遍不会访问到pos往上的点了 
    num=0;
    getdis(root,0,0);//在该子树下所有点到root的距离
    ans+=count();
    for(int p=head[ root];p!=-1;p=edge[p].next)
    {
        if(!visit[edge[p].to])
        {
            num=0;
            getdis(edge[p].to,0,edge[p].c);
            ans-=count();//在刚刚的统计中,如果两点的最小公共祖先是root的子节点,那么会重复计算,需要减掉
            //也就是说在当前的solve函数中只求最小公共祖先为pos且距离小于m的点对
        }
    }
    for(int p=head[root];p!=-1;p=edge[p].next)
    {
         if(!visit[edge[p].to])
        {
            solve(edge[p].to,root);//再次往下递归枚举
        }
    }
}

int main()
{int n,u,v,c;
    while(cin>>n>>m&&n+m)
    {
        memset(head,-1,sizeof(head));
        memset(visit,0,sizeof(visit));
        ip=0;
        for(int i=1;i<n;i++)
        {
            scanf("%d%d%d",&u,&v,&c);
            addedge(u,v,c);
            addedge(v,u,c);
        }
        visit[0]=1;
        ans=0;
        solve(1,0);
        cout<<ans<<endl;
    }
    return 0;
}

 
内容概要:本文档详细介绍了Analog Devices公司生产的AD8436真均方根-直流(RMS-to-DC)转换器的技术细节及其应用场景。AD8436由三个独立模块构成:轨到轨FET输入放大器、高动态范围均方根计算内核和精密轨到轨输出放大器。该器件不仅体积小巧、功耗低,而且具有广泛的输入电压范围和快速响应特性。文档涵盖了AD8436的工作原理、配置选项、外部组件选择(如电容)、增益调节、单电源供电、电流互感器配置、接地故障检测、三相电源监测等方面的内容。此外,还特别强调了PCB设计注意事项和误差源分析,旨在帮助工程师更好地理解和应用这款高性能的RMS-DC转换器。 适合人群:从事模拟电路设计的专业工程师和技术人员,尤其是那些需要精确测量交流电信号均方根值的应用开发者。 使用场景及目标:①用于工业自动化、医疗设备、电力监控等领域,实现对交流电压或电流的精准测量;②适用于手持式数字万用表及其他便携式仪器仪表,提供高效的单电源解决方案;③在电流互感器配置中,用于检测微小的电流变化,保障电气安全;④应用于三相电力系统监控,优化建立时间和转换精度。 其他说明:为了确保最佳性能,文档推荐使用高质量的电容器件,并给出了详细的PCB布局指导。同时提醒用户关注电介质吸收和泄漏电流等因素对测量准确性的影响。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值