Tree(树分治入门)

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

Tree
Time Limit: 1000MS Memory Limit: 30000K
Total Submissions: 35091 Accepted: 11718

Description

Give a tree with n vertices,each edge has a length(positive integer less than 1001).
Define dist(u,v)=The min distance between node u and v.
Give an integer k,for every pair (u,v) of vertices is called valid if and only if dist(u,v) not exceed k.
Write a program that will count how many pairs which are valid for a given tree.

Input

The input contains several test cases. The first line of each test case contains two integers n, k. (n<=10000) The following n-1 lines each contains three integers u,v,l, which means there is an edge between node u and v of length l.
The last test case is followed by two zeros.

Output

For each test case output the answer on a single line.

Sample Input

5 4
1 2 3
1 3 1
1 4 2
3 5 1
0 0

Sample Output

8

Source

 

题意:一棵有n个节点的树,每条边有个权值代表相邻2个点的距离,要求求出所有距离不超过k的点对(u,v)

题解:树分治
假设树以root为根节点,那么满足要求的点对有2种情况:
①路径经过root且dis(u,v)<=k
②路径不经过root,即其路径的最高点为子树上某一节点

对于第②种情况可以通过递归求解,这里只讨论第一种情况
该如何求解路径经过root且dis(u,v)<=k的合法点对数呢?

设dir[u]为u到根节点root的距离,那么只有满足dir[u]+dir[v]<=k且LCA(u,v)==root的点对才是合法的,
设cnt1=树中所有dis(u,v)<=k的点对数,cnt2=LCA(u,v)==root的子节点的合法点对数
那么以root为根的树种合法点对数为:ans=cnt1-cnt2
找出有多少个dir[u]+dir[v]的方法很简单:只需要排序后扫一遍即可。

总结一下算法的过程:
①计算以u为根的树种每棵子树的大小
②根据子树大小找出树的重心root(以树的重心为根的树,可以使其根的子树中节点最多的子树的节点最少)
③以root为根,计算树中每个点到root的距离dir
④计算树中所有满足dir[u]+dir[v]<=k的点对数cnt1
⑤计算以root的子节点为根的子树中,满足dir[u]+dir[v]<=k的点对数cnt2
⑥ans+=cnt1-cnt2
注意:每次计算完cnt1后,要将vis[root]=1,这样就可以将一棵树分解成若干棵子树

看代码:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<vector>
using namespace std;
typedef long long LL;
const int maxn=2e4+5;
const int INF=1e9+7;
int cnt=0;
int head[maxn<<1];
int sonnum[maxn<<1],sonmax[maxn<<1];
int mi,pos;
int N,K;
int sum=0;
bool vis[maxn<<1];
LL ans;
vector<int>dis;
struct Edge
{
    int next,to,w;
}e[maxn<<1];
void Init()
{
    ans=0;cnt=0;
    for(int i=0;i<maxn;i++)
    {
        head[i]=-1;sonnum[i]=sonmax[i]=0;
        vis[i]=false;
    }
}
void add_edge(int u,int v,int w)
{
    e[++cnt].to=v;
    e[cnt].w=w;
    e[cnt].next=head[u];
    head[u]=cnt;
}
void Query_size(int root,int pre)//求当前树的子树大小
{
    sum++;//存树的大小
    sonnum[root]=1;sonmax[root]=0;//注意初始化 
    for(int i=head[root];i!=-1;i=e[i].next)
    {
        int v=e[i].to;
        if(vis[v]||v==pre) continue;
        Query_size(v,root);
        sonnum[root]+=sonnum[v];//子树节点有多少个
        sonmax[root]=max(sonmax[root],sonnum[v]);//最大的子树节点个数
    }
}
void Query_root(int root,int pre,int sum)//求当前树的重心
{
    for(int i=head[root];i!=-1;i=e[i].next)
    {
        int v=e[i].to;
        if(vis[v]||v==pre) continue;
        Query_root(v,root,sum);
    }
    int ma=max(sonmax[root],sum-sonnum[root]);
    if(mi>ma)
    {
        mi=ma;pos=root;
    }
}
void Query_dis(int root,int pre,int d)
{
    dis.push_back(d);
    for(int i=head[root];i!=-1;i=e[i].next)
    {
        int v=e[i].to,w=e[i].w;
        if(vis[v]||v==pre) continue;
        Query_dis(v,root,d+w);
    }
}
int cal(int root,int d)
{
    int ret=0;
    dis.clear();//存所有子节点到本身的距离
    Query_dis(root,0,d);
    sort(dis.begin(),dis.end());
    int i=0,j=dis.size()-1;
    while(i<j)
    {
        while(i<j&&dis[i]+dis[j]>K) j--;
        ret+=j-i;
        i++;
    }
    return ret;
}
void dfs(int root,int pre)
{
    sum=0;
    mi=INF;
    Query_size(root,pre);
    Query_root(root,pre,sum);//
    int rt=pos;
    ans+=cal(rt,0);//pos为找到的重心
    vis[rt]=true;//一定要标记 否则会往回走
    for(int i=head[rt];i!=-1;i=e[i].next)
    {
        int v=e[i].to,w=e[i].w;
        if(vis[v]) continue;
        ans-=cal(v,w);
        dfs(v,rt);
    }
}
int main()
{
    while(scanf("%d%d",&N,&K)!=EOF)
    {
        Init();
        if(N==0&&K==0) break;
        for(int i=1;i<N;i++)
        {
            int u,v,w;scanf("%d%d%d",&u,&v,&w);
            add_edge(u,v,w);
            add_edge(v,u,w);
        }
        dfs(1,0);
        printf("%lld\n",ans);
    }
    return 0;
}

 

转载于:https://www.cnblogs.com/caijiaming/p/11569226.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Tree.js 是一个用于创建动态 3D 场景的开源 JavaScript 库。它可以让开发者在网页上直观地呈现出状结构的图形,并且支持对这些图形进行交互和动画,拥有强大的渲染能力和用户体验。如果想要入门 Tree.js,可以参考以下步骤: 1. 准备工作:首先,在开始学习 Tree.js 之前,需要具备一定的前端开发知识,包括 HTML、CSS 和 JavaScript。另外,需要确保已经安装了最新版的浏览器,以便于在学习和开发过程中进行实时预览和调试。 2. 下载和引入:在官网 https://threejs.org/ 上可以找到最新的 Tree.js 版本,可以选择下载或者通过 CDN 引入。然后在 HTML 文件中引入 Tree.js 的 JavaScript 文件。 3. 创建场景:在 JavaScript 中创建一个基本的 3D 场景,并添加相机、光源和几何体。这可以通过 Tree.js 提供的 API 来实现,例如使用 Scene、PerspectiveCamera、AmbientLight、DirectionalLight 和 BoxGeometry。 4. 渲染场景:在 JavaScript 中设置渲染器和将场景渲染到 HTML 中。可以使用 Renderer、CSS3DRenderer 和 WebGLRenderer 来实现不同的渲染效果。 5. 交互和动画:通过 JavaScript 实现鼠标交互、键盘事件或者自动动画效果,使得用户可以与场景进行交互。 6. 学习资源:Tree.js 官网提供了丰富的文档和示例,还有一个活跃的社区,可以在社区中学习和交流。 通过以上步骤,可以初步了解如何使用 Tree.js 创建一个基本的 3D 场景,并进行交互和动画。随着深入学习和实践,会发现 Tree.js 的强大和灵活,可以实现各种炫酷的 3D 效果。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值