POJ 1741 Tree 树的分治

题目大意:给你一棵树,边有权值,问距离不大于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;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值