POJ-1471-Tree(点分治)

48 篇文章 0 订阅
42 篇文章 0 订阅

题目链接:http://poj.org/problem?id=1741

大致题意:

给定一棵N(1<=N<=10000)个结点的带权树,定义dist(v,u)为

vu,两点间的最短路径长度,路径的长度定义为路径上所有边的权和。
再给定一个K(1<=K<=1e9)如果对于不同的两个结点a,b,如果满
足dist(a,b)<=K则称(a,b)为合法点对。 

求合法点对个数。 

大致题解:(参考国家集训队论文:http://wenku.baidu.com/link?url=7KOPn20aLvKK5PqDmuLjIyj4sqZ6CL1H9qP__JSGvX-AWgX7LR6gC-BZ3PTVCP2ojBHxKZcJ5U3csiRjuspqcoFJfswO7JaEIQyKlxwUzBi

如果使用普通的DFS遍历,时间复杂度高达O(N^2),而使用时
间复杂度为O(NK)的动态规划,更是无法在规定时限内出解的。 


我们知道一条路径要么过根结点,要么在一棵子树中,这启发了我们可以使用分治算法。

路径在子树中的情况只需递归处理即可,下面我们来分析如何处理路径过根结点的情况

记Depth(i)表示点i到根结点的路径长度,Belong(i)=X (X为根结点的某个儿子,且结点i在以X为根的子树内)。那么我们要统计的就是: 
   满足Depth(i)+Depth(j)<=K且Belong(i)!=Belong(j)的个数 
=满足Depth(i)+Depth(j)<=K的(i,j)个数 –满足Depth(i)+Depth(j)<=K且Belong(i)!=Belong(j)的(i,j)个数 

而对于这两个部分,都是要求出满足Ai+Aj<=k的(i,j)对数。将A排序后利用单调性我们很容易得出一个O(N)的算法,所以我们可以用O(NlogN)的时间来解决这个问题。 
综上,此题使用树的分治算法时间复杂度为O(Nlog^2N).




//#include <bits/stdc++.h>
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#define INF 0x3f3f3f3f
using namespace std;
const int MAXN = 1e4+7;
const int MAXM = 1e5+7;
const double eps = 1e-5;
int n,k;
struct node
{
    int v,next,cost;
} edge[MAXM];
int head[MAXM],index;
void add_edge(int u,int v,int c)
{
    edge[index].v=v;
    edge[index].cost=c;
    edge[index].next=head[u];
    head[u]=index++;
}
int vis[MAXN],siz[MAXN],mxe[MAXN],dis[MAXN];
int mmin,root,ans,num;
void get_size(int u,int father)
{
    siz[u]=1;
    mxe[u]=0;
    for(int i=head[u]; i+1; i=edge[i].next)
    {
        int v=edge[i].v;
        if(v!=father && !vis[v])
        {
            get_size(v,u);
            mxe[u]=max(mxe[u],siz[v]);
            siz[u]+=siz[v];
        }
    }
}
int p;
void get_root(int u,int father)//寻找树的重心
{
    if(siz[p]-siz[u]>mxe[u])mxe[u]=siz[p]-siz[u];
    if(mxe[u]<mmin)mmin=mxe[u],root=u;
    for(int i=head[u]; i+1; i=edge[i].next)
    {
        int v=edge[i].v;
        if(v!=father && !vis[v])
            get_root(v,u);
    }
}
void get_dis(int u,int father,int c)
{
    dis[num++]=c;
    for(int i=head[u]; i+1; i=edge[i].next)
    {
        int v=edge[i].v;
        if(v!=father && !vis[v])
            get_dis(v,u,edge[i].cost+c);
    }
}
int cal(int u,int c)
{
    int temp=0;
    num=0;
    get_dis(u,0,c);
    sort(dis,dis+num);
    int l=0,r=num-1;
    while(l<r)
    {
        while(dis[l]+dis[r]>k && l<r)r--;
        temp+=r-l;
        ++l;
    }
    return temp;
}
void DFS(int u)
{
    mmin=n;
    get_size(u,0);
    p=u;
    get_root(u,0);
    ans+=cal(root,0);
    vis[root]=1;
    for(int i=head[root]; i+1; i=edge[i].next)
    {
        int v=edge[i].v;
        if(!vis[v])
        {
            ans-=cal(v,edge[i].cost);//减掉(i,j)同在子树内的情况
            DFS(v);
        }
    }
}
int main()
{
    int a,b,c;
    while(~scanf("%d%d",&n,&k))
    {
        if(!n)break;
        index=ans=0;
        memset(head,-1,sizeof(head));
        memset(vis,0,sizeof(vis));
        for(int i=1; i<n; ++i)
        {
            scanf("%d%d%d",&a,&b,&c);
            add_edge(a,b,c);
            add_edge(b,a,c);
        }
        DFS(1);
        printf("%d\n",ans);
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值